File: /opt/aphex/sites/casinoqa.com/backup/finnish-network-global/finnish-network-global.php
<?php
/**
* Plugin Name: Advanced Content Scheduling Aphex
* Plugin URI: https://example.com/finnish-network-global
* Description: Global content distribution system with multi-market support, AI rewriting, and intelligent scheduling.
* Version: 2.4.0
* Author: Aphex Deadpool
* Author URI: https://example.com
* Text Domain: finnish-network-global
* Domain Path: /languages
*/
// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
die;
}
// License Platform Configuration
define('FINNISH_LICENSE_PLATFORM_URL', 'https://ai.aphexmedia.com/tools/deadpool/api.php');
/**
* Current plugin version.
*/
define( 'FINNISH_NETWORK_AUTOMATION_VERSION', '1.0.0' );
/**
* The code that runs during plugin activation.
*/
function activate_finnish_global_automation() {
require_once plugin_dir_path( __FILE__ ) . 'includes/class-activator.php';
Finnish_Global_Automation_Activator::activate();
// Register with license platform
if (!get_option('finnish_global_activation_time')) {
update_option('finnish_global_activation_time', time());
}
require_once plugin_dir_path( __FILE__ ) . 'includes/class-license-manager.php';
$license_manager = new Finnish_Global_License_Manager();
$license_manager->register_with_platform();
}
/**
* The code that runs during plugin deactivation.
*/
function deactivate_finnish_global_automation() {
require_once plugin_dir_path( __FILE__ ) . 'includes/class-deactivator.php';
Finnish_Global_Automation_Deactivator::deactivate();
}
register_activation_hook( __FILE__, 'activate_finnish_global_automation' );
register_deactivation_hook( __FILE__, 'deactivate_finnish_global_automation' );
/**
* The core plugin class that is used to define internationalization,
* admin-specific hooks, and public-facing site hooks.
*/
require_once plugin_dir_path( __FILE__ ) . 'includes/class-logger.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/class-finnish-network-global.php';
/**
* Begins execution of the plugin.
*/
function run_finnish_global_automation_direct() {
// Load Dependencies (Services & Logic)
require_once plugin_dir_path( __FILE__ ) . 'includes/class-loader.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/class-i18n.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/class-openai-service.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/class-pexels-service.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/class-dataforseo-service.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/class-rss-fetcher.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/class-content-processor.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/class-scheduler.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/class-cronjob-service.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/class-license-manager.php'; // License system
// AI Enhancement Modules
require_once plugin_dir_path( __FILE__ ) . 'includes/class-perplexity-service.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/module-perplexity-integration.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/module-ai-images.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/module-seo-validation.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/module-content-manager.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/module-rss-cleanup.php';
// require_once plugin_dir_path( __FILE__ ) . 'includes/module-enhancement-controller.php'; // DISABLED: Redundant - all modules loaded directly above
require_once plugin_dir_path( __FILE__ ) . 'includes/module-evergreen-reviver.php';
// New modules for URL Rewriter and Category Scraper
require_once plugin_dir_path( __FILE__ ) . 'includes/class-url-scraper-service.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/class-content-processor-modules.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/module-url-rewriter.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/module-category-scraper.php';
// AI Community Manager Module
require_once plugin_dir_path( __FILE__ ) . 'includes/module-ai-community-manager.php';
// Internal Linking Intelligence Module
require_once plugin_dir_path( __FILE__ ) . 'includes/module-internal-linking-intelligence.php';
// Video Embedding Intelligence Module
require_once plugin_dir_path( __FILE__ ) . 'includes/module-video-embedding-intelligence.php';
// Debug file (remove in production)
// require_once plugin_dir_path( __FILE__ ) . 'debug-modules.php';
// Initialize Scheduler for Cron
$scheduler = new Finnish_Global_Scheduler();
add_action( 'finnish_global_fetch_cron', array( $scheduler, 'run_fetch_cycle' ) );
add_action( 'finnish_global_generate_on_day', array( $scheduler, 'execute_daily_generation' ) );
add_action( 'finnish_global_run_scheduler_task', array( $scheduler, 'run_scheduler_task' ) );
// Ensure Cron Schedule
if ( ! wp_next_scheduled( 'finnish_global_fetch_cron' ) ) {
wp_schedule_event( time(), 'hourly', 'finnish_global_fetch_cron' );
}
// Enhancement Controller is now redundant - Content Manager handles everything
// Initialize Video Embedding Intelligence Module
add_action('init', function() {
if (class_exists('Finnish_Global_Video_Embedding_Intelligence')) {
$GLOBALS['finnish_global_video_embedding'] = new Finnish_Global_Video_Embedding_Intelligence();
}
});
// ADMIN UI LOGIC (INLINED)
add_action( 'admin_menu', function() {
add_menu_page(
'Deadpool Content Bonanza',
'Deadpool Content',
'manage_options',
'finnish-network-global',
'finnish_global_render_dashboard',
'dashicons-networking',
30
);
add_submenu_page(
'finnish-network-global',
'Settings',
'Settings',
'manage_options',
'finnish_global_automation_options',
function() {
if ( function_exists( 'finnish_global_render_dashboard' ) ) {
finnish_global_render_dashboard( 'settings' );
} else {
echo '<p>Error: Dashboard function not found.</p>';
}
}
);
add_settings_section(
'finnish_global_api_settings',
'API Configuration',
null,
'finnish_global_automation_options'
);
});
add_action( 'admin_enqueue_scripts', function( $hook ) {
if ( strpos( $hook, 'finnish-network-global' ) !== false ) {
wp_enqueue_style( 'finnish-global-admin', plugin_dir_url( __FILE__ ) . 'admin/css/finnish-network-global-admin.css', array(), '2.0.0', 'all' );
$version = '2.1.49'; // Use plugin version for script version
wp_enqueue_script( 'finnish-network-global-admin', plugin_dir_url( __FILE__ ) . 'admin/js/finnish-network-global-admin.js', array( 'jquery' ), $version, false );
}
});
// Output Schema Markup in wp_head
add_action( 'wp_head', function() {
if ( ! is_single() ) return;
global $post;
$schema_json = get_post_meta( $post->ID, '_schema_markup', true );
if ( ! empty( $schema_json ) ) {
echo "\n<script type=\"application/ld+json\">\n";
echo $schema_json;
echo "\n</script>\n";
}
});
add_action( 'admin_init', function() {
register_setting( 'finnish_global_automation_options', 'finnish_global_openai_key' );
register_setting( 'finnish_global_automation_options', 'finnish_global_scraper_api_key' );
register_setting( 'finnish_global_automation_options', 'finnish_global_pexels_key' );
register_setting( 'finnish_global_automation_options', 'finnish_global_dataforseo_key' );
register_setting( 'finnish_global_automation_options', 'finnish_global_dataforseo_login' );
register_setting( 'finnish_global_automation_options', 'finnish_global_target_market' );
register_setting( 'finnish_global_automation_options', 'finnish_global_cronjob_api_key' );
register_setting( 'finnish_global_automation_options', 'finnish_global_cronjob_id' );
// AI Prompts
register_setting( 'finnish_global_automation_options', 'finnish_global_prompt_content' );
register_setting( 'finnish_global_automation_options', 'finnish_global_prompt_title' );
register_setting( 'finnish_global_automation_options', 'finnish_global_prompt_meta' );
// v2.1.55 Features
register_setting( 'finnish_global_automation_options', 'finnish_global_enable_schema' );
register_setting( 'finnish_global_automation_options', 'finnish_global_enable_keyword_density' );
register_setting( 'finnish_global_automation_options', 'finnish_global_min_density' );
register_setting( 'finnish_global_automation_options', 'finnish_global_max_density' );
register_setting( 'finnish_global_automation_options', 'finnish_global_enable_style_mixer' );
// v2.3.0 Features
register_setting( 'finnish_global_automation_options', 'finnish_global_enable_internal_links' );
register_setting( 'finnish_global_automation_options', 'finnish_global_enable_dataforseo' ); // New setting for v2.4.0
register_setting( 'finnish_global_automation_options', 'finnish_global_enable_evergreen_slack' ); // Evergreen Slack notifications
// v2.4.0 Features
register_setting( 'finnish_global_automation_options', 'finnish_global_enable_fact_check' );
register_setting( 'finnish_global_automation_options', 'finnish_global_check_uniqueness' );
register_setting( 'finnish_global_automation_options', 'finnish_global_style_presets' );
// AI Enhancement Features
register_setting( 'finnish_global_automation_options', 'finnish_global_perplexity_key' );
register_setting( 'finnish_global_automation_options', 'finnish_global_enable_perplexity' );
register_setting( 'finnish_global_automation_options', 'finnish_global_perplexity_model' );
register_setting( 'finnish_global_automation_options', 'finnish_global_perplexity_contextual_facts' );
register_setting( 'finnish_global_automation_options', 'finnish_global_perplexity_inline_links' );
register_setting( 'finnish_global_automation_options', 'finnish_global_perplexity_footnote_links' );
register_setting( 'finnish_global_automation_options', 'finnish_global_perplexity_link_style' );
register_setting( 'finnish_global_automation_options', 'finnish_global_image_strategy' );
register_setting( 'finnish_global_automation_options', 'finnish_global_enable_seo_validation' );
register_setting( 'finnish_global_automation_options', 'finnish_global_enable_duplicate_prevention' );
register_setting( 'finnish_global_automation_options', 'finnish_global_duplicate_action' );
register_setting( 'finnish_global_automation_options', 'finnish_global_auto_publish' );
// URL Rewriter Module Settings
register_setting( 'finnish_global_automation_options', 'url_rewriter_enable_perplexity' );
register_setting( 'finnish_global_automation_options', 'url_rewriter_enable_lsi' );
register_setting( 'finnish_global_automation_options', 'url_rewriter_enable_internal_linking' );
register_setting( 'finnish_global_automation_options', 'url_rewriter_enable_images' );
register_setting( 'finnish_global_automation_options', 'url_rewriter_enable_fact_check' );
register_setting( 'finnish_global_automation_options', 'url_rewriter_enable_uniqueness' );
register_setting( 'finnish_global_automation_options', 'url_rewriter_enable_seo_validation' );
register_setting( 'finnish_global_automation_options', 'url_rewriter_enable_schema' );
register_setting( 'finnish_global_automation_options', 'url_rewriter_enable_readability' );
register_setting( 'finnish_global_automation_options', 'url_rewriter_enable_keyword_density' );
register_setting( 'finnish_global_automation_options', 'url_rewriter_daily_limit' );
register_setting( 'finnish_global_automation_options', 'url_rewriter_default_author' );
register_setting( 'finnish_global_automation_options', 'url_rewriter_default_category' );
// Register settings for URL Rewriter module settings form
register_setting( 'finnish_global_url_rewriter_options', 'url_rewriter_enable_perplexity' );
register_setting( 'finnish_global_url_rewriter_options', 'url_rewriter_enable_lsi' );
register_setting( 'finnish_global_url_rewriter_options', 'url_rewriter_enable_internal_linking' );
register_setting( 'finnish_global_url_rewriter_options', 'url_rewriter_enable_images' );
register_setting( 'finnish_global_url_rewriter_options', 'url_rewriter_enable_seo_validation' );
register_setting( 'finnish_global_url_rewriter_options', 'url_rewriter_enable_uniqueness' );
register_setting( 'finnish_global_url_rewriter_options', 'url_rewriter_enable_slack_draft' );
register_setting( 'finnish_global_url_rewriter_options', 'url_rewriter_enable_slack_published' );
register_setting( 'finnish_global_url_rewriter_options', 'url_rewriter_image_strategy' );
// Register settings for Category Scraper module settings form
register_setting( 'finnish_global_category_scraper_options', 'category_scraper_enable_perplexity' );
register_setting( 'finnish_global_category_scraper_options', 'category_scraper_enable_lsi' );
register_setting( 'finnish_global_category_scraper_options', 'category_scraper_enable_internal_linking' );
register_setting( 'finnish_global_category_scraper_options', 'category_scraper_enable_images' );
register_setting( 'finnish_global_category_scraper_options', 'category_scraper_enable_seo_validation' );
register_setting( 'finnish_global_category_scraper_options', 'category_scraper_enable_uniqueness' );
register_setting( 'finnish_global_category_scraper_options', 'category_scraper_enable_slack_draft' );
register_setting( 'finnish_global_category_scraper_options', 'category_scraper_enable_slack_published' );
register_setting( 'finnish_global_category_scraper_options', 'category_scraper_image_strategy' );
// Category Scraper Module Settings
register_setting( 'finnish_global_automation_options', 'category_scraper_enable_perplexity' );
register_setting( 'finnish_global_automation_options', 'category_scraper_enable_lsi' );
register_setting( 'finnish_global_automation_options', 'category_scraper_enable_internal_linking' );
register_setting( 'finnish_global_automation_options', 'category_scraper_enable_images' );
register_setting( 'finnish_global_automation_options', 'category_scraper_enable_fact_check' );
register_setting( 'finnish_global_automation_options', 'category_scraper_enable_uniqueness' );
register_setting( 'finnish_global_automation_options', 'category_scraper_enable_seo_validation' );
register_setting( 'finnish_global_automation_options', 'category_scraper_enable_schema' );
register_setting( 'finnish_global_automation_options', 'category_scraper_enable_readability' );
register_setting( 'finnish_global_automation_options', 'category_scraper_enable_keyword_density' );
register_setting( 'finnish_global_automation_options', 'category_scraper_daily_limit' );
register_setting( 'finnish_global_automation_options', 'category_scraper_default_author' );
register_setting( 'finnish_global_automation_options', 'category_scraper_default_category' );
add_settings_field(
'finnish_global_target_market',
'Target Market',
'finnish_global_render_target_market_field',
'finnish_global_automation_options',
'finnish_global_api_settings',
array( 'label_for' => 'finnish_global_target_market' )
);
add_settings_field(
'finnish_global_openai_key',
'OpenAI API Key',
'finnish_global_render_text_field',
'finnish_global_automation_options',
'finnish_global_api_settings',
array( 'label_for' => 'finnish_global_openai_key', 'name' => 'finnish_global_openai_key' )
);
add_settings_field(
'finnish_global_pexels_key',
'Pexels API Key',
'finnish_global_render_text_field',
'finnish_global_automation_options',
'finnish_global_api_settings',
array( 'label_for' => 'finnish_global_pexels_key', 'name' => 'finnish_global_pexels_key' )
);
add_settings_field(
'finnish_global_scraper_api_key',
'ScraperAPI Key (Optional)',
'finnish_global_render_text_field',
'finnish_global_automation_options',
'finnish_global_api_settings',
array(
'label_for' => 'finnish_global_scraper_api_key',
'name' => 'finnish_global_scraper_api_key',
'description' => 'Get from scraperapi.com - Enables scraping of protected sites. Free tier: 5k requests/month.'
)
);
/* HIDDEN: DataForSEO settings temporarily hidden from UI (functionality preserved)
add_settings_field(
'finnish_global_enable_dataforseo',
'Enable DataForSEO',
'finnish_global_render_checkbox_field',
'finnish_global_automation_options',
'finnish_global_api_settings',
array(
'label_for' => 'finnish_global_enable_dataforseo',
'name' => 'finnish_global_enable_dataforseo',
'description' => 'Enable for English, Spanish, German, French. <strong>Disable for Finnish</strong> (use AI keywords instead).'
)
);
add_settings_field(
'finnish_global_dataforseo_login',
'DataForSEO Login',
'finnish_global_render_text_field',
'finnish_global_automation_options',
'finnish_global_api_settings',
array( 'label_for' => 'finnish_global_dataforseo_login', 'name' => 'finnish_global_dataforseo_login' )
);
add_settings_field(
'finnish_global_dataforseo_key',
'DataForSEO API Password',
'finnish_global_render_text_field',
'finnish_global_automation_options',
'finnish_global_api_settings',
array( 'label_for' => 'finnish_global_dataforseo_key', 'name' => 'finnish_global_dataforseo_key' )
);
*/
// Slack Settings Section
add_settings_section(
'finnish_global_slack_settings',
'Slack Notifications',
null,
'finnish_global_automation_options'
);
register_setting( 'finnish_global_automation_options', 'finnish_global_slack_token' );
register_setting( 'finnish_global_automation_options', 'finnish_global_slack_channel_id' );
register_setting( 'finnish_global_automation_options', 'finnish_global_slack_webhook_url' );
register_setting( 'finnish_global_automation_options', 'finnish_global_slack_triggers' );
add_settings_field(
'finnish_global_slack_token',
'Slack Bot Token (xoxb-...)',
'finnish_global_render_text_field',
'finnish_global_automation_options',
'finnish_global_slack_settings',
array( 'label_for' => 'finnish_global_slack_token', 'name' => 'finnish_global_slack_token' )
);
add_settings_field(
'finnish_global_slack_channel_id',
'Target Channel',
'finnish_global_render_slack_channel_field',
'finnish_global_automation_options',
'finnish_global_slack_settings'
);
add_settings_field(
'finnish_global_slack_triggers',
'Notify Me When...',
'finnish_global_render_slack_triggers_field',
'finnish_global_automation_options',
'finnish_global_slack_settings'
);
// License Settings Section
add_settings_section(
'finnish_global_license_settings',
'License Activation',
null,
'finnish_global_automation_options'
);
// AI Enhancement Settings Section
add_settings_section(
'finnish_global_enhancement_settings',
'AI Enhancement Features',
null,
'finnish_global_automation_options'
);
register_setting( 'finnish_global_automation_options', 'finnish_global_license_key' );
add_settings_field(
'finnish_global_license_key',
'License Key',
'finnish_global_render_license_field',
'finnish_global_automation_options',
'finnish_global_license_settings'
);
// AI Enhancement Settings Fields
add_settings_field(
'finnish_global_perplexity_key',
'Perplexity API Key',
'finnish_global_render_perplexity_key_field',
'finnish_global_automation_options',
'finnish_global_enhancement_settings'
);
add_settings_field(
'finnish_global_enable_perplexity',
'Enable Fact-Checking',
'finnish_global_render_enable_perplexity_field',
'finnish_global_automation_options',
'finnish_global_enhancement_settings'
);
add_settings_field(
'finnish_global_perplexity_model',
'Perplexity Model',
'finnish_global_render_perplexity_model_field',
'finnish_global_automation_options',
'finnish_global_enhancement_settings'
);
add_settings_field(
'finnish_global_perplexity_contextual_facts',
'Contextual Facts (0-5)',
'finnish_global_render_perplexity_facts_field',
'finnish_global_automation_options',
'finnish_global_enhancement_settings'
);
add_settings_field(
'finnish_global_perplexity_inline_links',
'Inline Authority Links (0-10)',
'finnish_global_render_perplexity_inline_field',
'finnish_global_automation_options',
'finnish_global_enhancement_settings'
);
add_settings_field(
'finnish_global_perplexity_footnote_links',
'Footnote Reference Links (0-10)',
'finnish_global_render_perplexity_footnote_field',
'finnish_global_automation_options',
'finnish_global_enhancement_settings'
);
add_settings_field(
'finnish_global_perplexity_link_style',
'Authority Link Style',
'finnish_global_render_perplexity_link_style_field',
'finnish_global_automation_options',
'finnish_global_enhancement_settings'
);
add_settings_field(
'finnish_global_image_strategy',
'Image Generation Strategy',
'finnish_global_render_image_strategy_field',
'finnish_global_automation_options',
'finnish_global_enhancement_settings'
);
add_settings_field(
'finnish_global_enable_seo_validation',
'Enable SEO Character Limits',
'finnish_global_render_enable_seo_field',
'finnish_global_automation_options',
'finnish_global_enhancement_settings'
);
add_settings_field(
'finnish_global_enable_duplicate_prevention',
'Enable Duplicate Prevention',
'finnish_global_render_enable_duplicate_prevention_field',
'finnish_global_automation_options',
'finnish_global_enhancement_settings'
);
add_settings_field(
'finnish_global_duplicate_action',
'Duplicate Action',
'finnish_global_render_duplicate_action_field',
'finnish_global_automation_options',
'finnish_global_enhancement_settings'
);
add_settings_field(
'finnish_global_auto_publish',
'Auto-Publish High Quality Content',
'finnish_global_render_auto_publish_field',
'finnish_global_automation_options',
'finnish_global_enhancement_settings'
);
});
add_action( 'init', function() {
$labels = array(
'name' => 'Automation Feeds',
'singular_name' => 'Automation Feed',
'menu_name' => 'Feeds'
);
register_post_type( 'automation_feed', array(
'labels' => $labels,
'public' => false,
'show_ui' => true,
'show_in_menu' => 'finnish-network-global',
'supports' => array( 'title', 'custom-fields' )
));
});
add_action( 'add_meta_boxes', function() {
add_meta_box(
'finnish_global_feed_details',
'Feed Configuration',
'finnish_global_render_feed_meta_box',
'automation_feed',
'normal',
'high'
);
});
add_action( 'save_post', 'finnish_global_save_feed_meta_box' );
}
// RENDER FUNCTIONS
add_action( 'admin_init', 'finnish_global_handle_form_submissions' );
function finnish_global_handle_form_submissions() {
// Handle Form Submission (Auto-Schedule)
if ( isset( $_POST['action'] ) && $_POST['action'] === 'finnish_global_auto_schedule' ) {
if ( ! isset( $_POST['finnish_global_schedule_nonce'] ) || ! wp_verify_nonce( $_POST['finnish_global_schedule_nonce'], 'finnish_global_schedule_action' ) ) {
wp_die( 'Security check failed' );
}
$scheduler = new Finnish_Global_Scheduler();
$days = intval( $_POST['days_ahead'] );
$per_day = intval( $_POST['posts_per_day'] );
$count = $scheduler->auto_schedule_drafts( $days, $per_day );
// Redirect to avoid resubmission
wp_redirect( admin_url( 'admin.php?page=finnish-network-global&tab=scheduler&message=scheduled&count=' . $count ) );
exit;
}
// Handle Manual Fetch
if ( isset( $_POST['action'] ) && $_POST['action'] === 'finnish_global_manual_fetch' ) {
if ( ! isset( $_POST['finnish_global_fetch_nonce'] ) || ! wp_verify_nonce( $_POST['finnish_global_fetch_nonce'], 'finnish_global_fetch_action' ) ) {
wp_die( 'Security check failed' );
}
if ( function_exists( 'set_time_limit' ) ) @set_time_limit( 0 );
add_filter( 'wp_feed_cache_transient_lifetime', function() { return 0; } );
$scheduler = new Finnish_Global_Scheduler();
$scheduler->run_fetch_cycle();
wp_redirect( admin_url( 'admin.php?page=finnish-network-global&tab=dashboard&view=incoming&message=fetched' ) );
exit;
}
// Handle Generate Content (Single)
if ( isset( $_GET['action'] ) && $_GET['action'] === 'finnish_global_generate_content' && isset( $_GET['post_id'] ) ) {
$post_id = intval( $_GET['post_id'] );
check_admin_referer( 'finnish_global_generate_content_' . $post_id );
if ( function_exists( 'set_time_limit' ) ) @set_time_limit( 0 );
$processor = new Finnish_Global_Content_Processor();
$result = $processor->generate_content_for_post( $post_id );
if ( ! is_wp_error( $result ) ) {
$view = isset( $_GET['view'] ) ? $_GET['view'] : 'incoming';
wp_redirect( admin_url( 'admin.php?page=finnish-network-global&tab=dashboard&view=' . $view . '&message=generated' ) );
exit;
} else {
wp_die( 'Generation failed: ' . $result->get_error_message() );
}
}
// Handle Delete Content (Single)
if ( isset( $_GET['action'] ) && $_GET['action'] === 'finnish_global_delete_content' && isset( $_GET['post_id'] ) ) {
$post_id = intval( $_GET['post_id'] );
check_admin_referer( 'finnish_global_delete_content_' . $post_id );
wp_delete_post( $post_id, true );
$view = isset( $_GET['view'] ) ? $_GET['view'] : 'dashboard';
// If view is not set in URL (e.g. from some links), default to dashboard or try to detect
if ( ! isset( $_GET['view'] ) && isset( $_SERVER['HTTP_REFERER'] ) ) {
parse_str( parse_url( $_SERVER['HTTP_REFERER'], PHP_URL_QUERY ), $queries );
if ( isset( $queries['view'] ) ) $view = $queries['view'];
}
wp_redirect( admin_url( 'admin.php?page=finnish-network-global&tab=dashboard&view=' . $view . '&message=deleted' ) );
exit;
}
// Handle Bulk Actions (Incoming Tab)
if ( isset( $_POST['action'] ) && $_POST['action'] === 'finnish_global_bulk_incoming_action' ) {
if ( ! isset( $_POST['finnish_global_bulk_incoming_nonce'] ) || ! wp_verify_nonce( $_POST['finnish_global_bulk_incoming_nonce'], 'finnish_global_bulk_incoming_action' ) ) {
wp_die( 'Security check failed' );
}
if ( function_exists( 'set_time_limit' ) ) @set_time_limit( 0 );
$bulk_action = sanitize_text_field( $_POST['bulk_action_selector'] );
$post_ids = isset( $_POST['post_ids'] ) ? array_map( 'intval', $_POST['post_ids'] ) : array();
if ( ! empty( $post_ids ) ) {
$count = 0;
if ( $bulk_action === 'generate' ) {
$processor = new Finnish_Global_Content_Processor();
foreach ( $post_ids as $pid ) {
$processor->generate_content_for_post( $pid );
$count++;
}
wp_redirect( admin_url( 'admin.php?page=finnish-network-global&tab=dashboard&view=incoming&message=bulk_generated&count=' . $count ) );
exit;
} elseif ( $bulk_action === 'delete' ) {
foreach ( $post_ids as $pid ) {
wp_delete_post( $pid, true );
$count++;
}
wp_redirect( admin_url( 'admin.php?page=finnish-network-global&tab=dashboard&view=incoming&message=bulk_deleted&count=' . $count ) );
exit;
}
}
}
// Handle Quick Schedule
if ( isset( $_POST['action'] ) && $_POST['action'] === 'finnish_global_quick_schedule' ) {
if ( ! isset( $_POST['finnish_global_quick_schedule_nonce'] ) || ! wp_verify_nonce( $_POST['finnish_global_quick_schedule_nonce'], 'finnish_global_quick_schedule_action' ) ) {
wp_die( 'Security check failed' );
}
$post_id = intval( $_POST['post_id'] );
$date = sanitize_text_field( $_POST['schedule_date'] );
if ( $post_id && $date ) {
$update_args = array(
'ID' => $post_id,
'post_date' => $date,
'post_date_gmt' => get_gmt_from_date( $date ),
'post_status' => 'future',
);
wp_update_post( $update_args );
wp_redirect( admin_url( 'admin.php?page=finnish-network-global&tab=dashboard&view=drafts&message=scheduled' ) );
exit;
}
}
// Handle Bulk Schedule Range
if ( isset( $_POST['action'] ) && $_POST['action'] === 'finnish_global_bulk_schedule_range' ) {
if ( ! isset( $_POST['finnish_global_bulk_range_nonce'] ) || ! wp_verify_nonce( $_POST['finnish_global_bulk_range_nonce'], 'finnish_global_bulk_range_action' ) ) {
wp_die( 'Security check failed' );
}
if ( function_exists( 'set_time_limit' ) ) @set_time_limit( 0 );
$scheduler = new Finnish_Global_Scheduler();
$start_date = sanitize_text_field( $_POST['start_date'] );
$end_date = sanitize_text_field( $_POST['end_date'] );
$posts_per_day = intval( $_POST['posts_per_day'] );
$feed_id = !empty( $_POST['feed_id'] ) ? intval( $_POST['feed_id'] ) : null;
$fetch_fresh = isset( $_POST['fetch_fresh'] ) ? true : false;
$time_strategy = isset( $_POST['time_strategy'] ) ? sanitize_text_field( $_POST['time_strategy'] ) : 'random';
$time_strategy = isset( $_POST['time_strategy'] ) ? sanitize_text_field( $_POST['time_strategy'] ) : 'random';
$specific_time = isset( $_POST['specific_time'] ) ? sanitize_text_field( $_POST['specific_time'] ) : '';
$frequency = isset( $_POST['frequency'] ) ? intval( $_POST['frequency'] ) : 1;
$category_id = !empty( $_POST['category_id'] ) ? intval( $_POST['category_id'] ) : null;
$ai_category = isset( $_POST['ai_category'] ) ? true : false;
if ( $ai_category ) $category_id = null; // AI overrides manual
if ( $start_date && $end_date && $posts_per_day > 0 ) {
if ( $fetch_fresh ) {
// JIT GENERATION: Schedule a cron event for each day in the range
$start = new DateTime( $start_date );
$end = new DateTime( $end_date );
$end->modify( '+1 day' );
$interval = DateInterval::createFromDateString( $frequency . ' day' );
$period = new DatePeriod( $start, $interval, $end );
$count = 0;
foreach ( $period as $dt ) {
$date_str = $dt->format( 'Y-m-d' );
$scheduler->schedule_future_generation( $date_str, $posts_per_day, $feed_id, $time_strategy, $specific_time, $category_id, $ai_category );
$count += $posts_per_day;
}
wp_redirect( admin_url( 'admin.php?page=finnish-network-global&tab=dashboard&view=scheduled&message=jit_scheduled&count=' . $count ) );
exit;
} else {
// IMMEDIATE GENERATION (Existing Logic)
$count = $scheduler->schedule_drafts_for_range( $start_date, $end_date, $posts_per_day, $feed_id, $time_strategy, $specific_time, $frequency, $category_id, $ai_category );
wp_redirect( admin_url( 'admin.php?page=finnish-network-global&tab=dashboard&view=scheduled&message=bulk_generated&count=' . $count ) );
exit;
}
}
}
// Handle Change Image
if ( isset( $_POST['action'] ) && $_POST['action'] === 'finnish_global_change_image' ) {
if ( ! isset( $_POST['finnish_global_change_image_nonce'] ) || ! wp_verify_nonce( $_POST['finnish_global_change_image_nonce'], 'finnish_global_change_image_action' ) ) {
wp_die( 'Security check failed' );
}
$post_id = intval( $_POST['post_id'] );
$query = sanitize_text_field( $_POST['image_query'] );
if ( $post_id ) {
$pexels = new Finnish_Global_Pexels_Service();
if ( empty( $query ) ) $query = get_the_title( $post_id );
$image_url = $pexels->search_image( $query );
if ( $image_url && ! is_wp_error( $image_url ) ) {
require_once( ABSPATH . 'wp-admin/includes/media.php' );
require_once( ABSPATH . 'wp-admin/includes/file.php' );
require_once( ABSPATH . 'wp-admin/includes/image.php' );
$desc = "Image for post $post_id";
$file_array = array();
// Clean filename logic (duplicated from processor for robustness here)
$parsed_url = parse_url( $image_url );
$path = isset( $parsed_url['path'] ) ? $parsed_url['path'] : $image_url;
$file_array['name'] = basename( $path );
if ( ! pathinfo( $file_array['name'], PATHINFO_EXTENSION ) ) $file_array['name'] .= '.jpg';
$tmp = download_url( $image_url );
if ( ! is_wp_error( $tmp ) ) {
$file_array['tmp_name'] = $tmp;
$id = media_handle_sideload( $file_array, $post_id, $desc );
if ( ! is_wp_error( $id ) ) {
set_post_thumbnail( $post_id, $id );
wp_redirect( admin_url( 'admin.php?page=finnish-network-global&tab=dashboard&view=drafts&message=image_updated' ) );
exit;
} else {
@unlink( $file_array['tmp_name'] );
wp_die( 'Failed to attach image: ' . $id->get_error_message() );
}
} else {
wp_die( 'Failed to download image.' );
}
} else {
wp_die( 'No image found for query: ' . esc_html( $query ) );
}
}
}
// Handle Delete Content
if ( isset( $_GET['action'] ) && $_GET['action'] === 'finnish_global_delete_content' && isset( $_GET['post_id'] ) ) {
$post_id = intval( $_GET['post_id'] );
check_admin_referer( 'finnish_global_delete_content_' . $post_id );
if ( current_user_can( 'delete_post', $post_id ) ) {
wp_delete_post( $post_id, true );
wp_delete_post( $post_id, true );
$view = isset( $_GET['view'] ) ? $_GET['view'] : 'dashboard';
wp_redirect( admin_url( 'admin.php?page=finnish-network-global&tab=dashboard&view=' . $view . '&message=deleted' ) );
exit;
} else {
wp_die( 'Permission denied.' );
}
}
// Handle Clear Logs
if ( isset( $_POST['action'] ) && $_POST['action'] === 'finnish_global_clear_logs' ) {
Finnish_Global_Logger::clear_logs();
wp_redirect( admin_url( 'admin.php?page=finnish-network-global&tab=dashboard&message=logs_cleared' ) );
exit;
}
// Handle Create/Update Scheduler
if ( isset( $_POST['action'] ) && $_POST['action'] === 'finnish_global_create_scheduler' ) {
if ( ! isset( $_POST['finnish_global_create_scheduler_nonce'] ) || ! wp_verify_nonce( $_POST['finnish_global_create_scheduler_nonce'], 'finnish_global_create_scheduler_action' ) ) {
wp_die( 'Security check failed' );
}
$name = sanitize_text_field( $_POST['scheduler_name'] );
// Handle combined content source and schedule type
$content_source = sanitize_text_field( $_POST['content_source'] );
$schedule_type = sanitize_text_field( $_POST['scheduler_type'] );
// For new modules, use content_source as the type
if ( in_array( $content_source, array( 'evergreen', 'url_rewriter', 'category_scraper' ) ) ) {
$final_type = $content_source;
} else {
$final_type = $schedule_type; // RSS feeds use schedule type
}
$settings = array(
'type' => $final_type,
'schedule_type' => $schedule_type, // Store original schedule type for new modules
'start_date' => sanitize_text_field( $_POST['start_date'] ),
'end_date' => sanitize_text_field( $_POST['end_date'] ),
'specific_dates'=> !empty($_POST['specific_dates']) ? array_map('trim', explode("\n", $_POST['specific_dates'])) : array(),
'posts_per_day' => intval( $_POST['posts_per_day'] ),
'feed_id' => !empty( $_POST['feed_id'] ) ? intval( $_POST['feed_id'] ) : null,
'time_strategy' => isset( $_POST['time_strategy'] ) ? sanitize_text_field( $_POST['time_strategy'] ) : 'random',
'specific_time' => isset( $_POST['specific_time'] ) ? sanitize_text_field( $_POST['specific_time'] ) : '',
'category_id' => !empty( $_POST['category_id'] ) ? intval( $_POST['category_id'] ) : null,
'author_id' => isset( $_POST['author_id'] ) ? intval( $_POST['author_id'] ) : 0,
'ai_category' => isset( $_POST['ai_category'] ) ? true : false,
'fetch_fresh' => isset( $_POST['fetch_fresh'] ) ? true : false,
'interval_days' => isset( $_POST['interval_days'] ) ? intval( $_POST['interval_days'] ) : 1, // v2.4.0
'min_post_age' => isset( $_POST['min_post_age'] ) ? intval( $_POST['min_post_age'] ) : 180,
'perplexity_facts' => isset( $_POST['perplexity_facts'] ) ? true : false,
'perplexity_links' => isset( $_POST['perplexity_links'] ) ? true : false,
'scheduler_style_mixer' => !empty( $_POST['scheduler_style_mixer'] ) ? sanitize_text_field( $_POST['scheduler_style_mixer'] ) : '', // NEW: Per-scheduler Style Mixer
'quotable_content_enabled' => isset( $_POST['quotable_content_enabled'] ) ? true : false, // NEW: Quotable Content Enhancement
'category_sources' => !empty( $_POST['category_sources'] ) ? array_map( 'sanitize_text_field', $_POST['category_sources'] ) : array(), // NEW: Category Scraper source selection
'url_source_filters' => !empty( $_POST['url_source_filters'] ) ? array_map( 'sanitize_text_field', $_POST['url_source_filters'] ) : array(), // URL Rewriter source filters
'content_structure' => isset( $_POST['content_structure'] ) ? sanitize_text_field( $_POST['content_structure'] ) : 'strict', // NEW: Content structure mode (strict/natural)
);
$scheduler = new Finnish_Global_Scheduler();
if ( ! empty( $_POST['scheduler_id'] ) ) {
// Update existing
$id = sanitize_text_field( $_POST['scheduler_id'] );
$scheduler->update_scheduler( $id, array( 'name' => $name, 'settings' => $settings ) );
$msg = 'scheduler_updated';
} else {
// Create new
$scheduler->create_scheduler( $name, $settings );
$msg = 'scheduler_created';
}
// Save Evergreen Slack notification setting (global setting)
if ( isset( $_POST['finnish_global_enable_evergreen_slack'] ) ) {
update_option( 'finnish_global_enable_evergreen_slack', true );
} else {
update_option( 'finnish_global_enable_evergreen_slack', false );
}
wp_redirect( admin_url( 'admin.php?page=finnish-network-global&tab=scheduler&message=' . $msg ) );
exit;
}
// Handle Scheduler Actions (Pause/Resume/Delete/Run Now)
$valid_scheduler_actions = array('pause_scheduler', 'resume_scheduler', 'delete_scheduler', 'run_now_scheduler');
if ( isset( $_GET['action'] ) && in_array( $_GET['action'], $valid_scheduler_actions ) && isset( $_GET['id'] ) ) {
check_admin_referer( 'finnish_global_scheduler_action' );
$scheduler = new Finnish_Global_Scheduler();
$id = sanitize_text_field( $_GET['id'] );
if ( $_GET['action'] === 'pause_scheduler' ) {
$scheduler->update_scheduler( $id, array( 'status' => 'paused' ) );
add_settings_error( 'finnish_global_messages', 'finnish_global_message', 'Scheduler paused.', 'updated' );
} elseif ( $_GET['action'] === 'resume_scheduler' ) {
$scheduler->update_scheduler( $id, array( 'status' => 'active' ) );
add_settings_error( 'finnish_global_messages', 'finnish_global_message', 'Scheduler resumed.', 'updated' );
} elseif ( $_GET['action'] === 'delete_scheduler' ) {
$scheduler->delete_scheduler( $id );
add_settings_error( 'finnish_global_messages', 'finnish_global_message', 'Scheduler deleted.', 'updated' );
} elseif ( $_GET['action'] === 'run_now_scheduler' ) {
$scheduler->run_now( $id );
add_settings_error( 'finnish_global_messages', 'finnish_global_message', 'Scheduler triggered manually. Check logs for details.', 'updated' );
}
// Redirect to avoid re-submission
wp_redirect( admin_url( 'admin.php?page=finnish-network-global&tab=scheduler' ) );
exit;
}
}
function finnish_global_render_dashboard( $forced_tab = null ) {
// Display Messages
if ( isset( $_GET['message'] ) ) {
if ( $_GET['message'] === 'generated' ) echo '<div class="notice notice-success is-dismissible"><p>Content generated successfully! Moved to Drafts.</p></div>';
if ( $_GET['message'] === 'fetched' ) echo '<div class="notice notice-success is-dismissible"><p>Fetch cycle completed. Check "Incoming" tab.</p></div>';
if ( $_GET['message'] === 'deleted' ) echo '<div class="notice notice-success is-dismissible"><p>Content deleted successfully.</p></div>';
if ( $_GET['message'] === 'bulk_generated' ) echo '<div class="notice notice-success is-dismissible"><p>Bulk generation started for ' . intval($_GET['count']) . ' items. Check Drafts shortly.</p></div>';
if ( $_GET['message'] === 'jit_scheduled' ) echo '<div class="notice notice-success is-dismissible"><p>Just-in-Time Generation Scheduled! ' . intval($_GET['count']) . ' posts will be generated on their respective days.</p></div>';
if ( $_GET['message'] === 'jit_deleted' ) echo '<div class="notice notice-success is-dismissible"><p>Auto-Generation task deleted.</p></div>';
if ( $_GET['message'] === 'scheduler_created' ) echo '<div class="notice notice-success is-dismissible"><p>New Scheduler Created!</p></div>';
if ( $_GET['message'] === 'scheduler_updated' ) echo '<div class="notice notice-success is-dismissible"><p>Scheduler Updated!</p></div>';
if ( $_GET['message'] === 'scheduler_paused' ) echo '<div class="notice notice-warning is-dismissible"><p>Scheduler Paused.</p></div>';
if ( $_GET['message'] === 'scheduler_resumed' ) echo '<div class="notice notice-success is-dismissible"><p>Scheduler Resumed.</p></div>';
if ( $_GET['message'] === 'scheduler_deleted' ) echo '<div class="notice notice-success is-dismissible"><p>Scheduler Deleted.</p></div>';
echo '<div class="notice notice-info is-dismissible"><p><strong>Deadpool Content Bonanza</strong> (v2.1.32) is active. <a href="' . admin_url( 'admin.php?page=finnish-network-global' ) . '">Go to Dashboard</a></p></div>';
if ( $_GET['message'] === 'bulk_deleted' ) echo '<div class="notice notice-success is-dismissible"><p>Bulk deleted ' . intval($_GET['count']) . ' items.</p></div>';
}
if ( $forced_tab ) {
$active_tab = $forced_tab;
} else {
$active_tab = isset( $_GET['tab'] ) ? $_GET['tab'] : 'dashboard';
}
?>
<style>
.finnish-network-wrap { max-width: 1200px; margin: 20px 0; }
.finnish-network-header { background: #fff; padding: 20px; border-bottom: 1px solid #ccd0d4; margin-bottom: 20px; box-shadow: 0 1px 1px rgba(0,0,0,0.04); }
.finnish-network-header h1 { margin: 0; color: #23282d; display: inline-block; margin-right: 20px; }
.finnish-network-version { background: #0073aa; color: #fff; padding: 2px 8px; border-radius: 10px; font-size: 12px; vertical-align: middle; }
.finnish-network-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; }
.finnish-network-card { background: #fff; border: 1px solid #ccd0d4; padding: 20px; box-shadow: 0 1px 1px rgba(0,0,0,0.04); }
.finnish-network-card h2 { margin-top: 0; border-bottom: 1px solid #eee; padding-bottom: 10px; font-size: 1.2em; }
.finnish-network-stat { font-size: 2em; font-weight: bold; color: #0073aa; margin: 10px 0; }
.finnish-network-stat-label { color: #666; font-size: 0.9em; text-transform: uppercase; letter-spacing: 0.5px; }
.finnish-network-actions { margin-top: 20px; padding-top: 20px; border-top: 1px solid #eee; }
.finnish-network-notice { background: #fff; border-left: 4px solid #00a32a; padding: 1px 12px; margin: 5px 0 15px; box-shadow: 0 1px 1px rgba(0,0,0,0.04); }
/* Status indicators for module tabs */
.status-success { color: #00a32a; font-weight: bold; }
.status-error { color: #d63638; font-weight: bold; }
.status-pending { color: #dba617; font-weight: bold; }
/* Fix for footer overlap */
#wpfooter { position: relative !important; }
</style>
<div class="wrap finnish-network-wrap">
<div class="finnish-network-header">
<h1>Deadpool Content Bonanza <span class="finnish-global-version">v2.1.35</span></h1>
<h2 class="nav-tab-wrapper">
<a href="?page=finnish-network-global&tab=dashboard" class="nav-tab <?php echo $active_tab == 'dashboard' ? 'nav-tab-active' : ''; ?>">Dashboard</a>
<a href="?page=finnish-network-global&tab=content" class="nav-tab <?php echo $active_tab == 'content' ? 'nav-tab-active' : ''; ?>">Content</a>
<a href="?page=finnish-network-global&tab=research" class="nav-tab <?php echo $active_tab == 'research' ? 'nav-tab-active' : ''; ?>">Research</a>
<a href="?page=finnish-network-global&tab=feeds" class="nav-tab <?php echo $active_tab == 'feeds' ? 'nav-tab-active' : ''; ?>">Feeds</a>
<a href="?page=finnish-network-global&tab=scheduler" class="nav-tab <?php echo $active_tab == 'scheduler' ? 'nav-tab-active' : ''; ?>">Scheduler</a>
<a href="?page=finnish-network-global&tab=url_rewriter" class="nav-tab <?php echo $active_tab == 'url_rewriter' ? 'nav-tab-active' : ''; ?>">URL Rewriter</a>
<a href="?page=finnish-network-global&tab=category_scraper" class="nav-tab <?php echo $active_tab == 'category_scraper' ? 'nav-tab-active' : ''; ?>">Category Scraper</a>
<a href="?page=finnish-network-global&tab=ai_community" class="nav-tab <?php echo $active_tab == 'ai_community' ? 'nav-tab-active' : ''; ?>">🤖 AI Community</a>
<a href="?page=finnish-network-global&tab=internal_linking" class="nav-tab <?php echo $active_tab == 'internal_linking' ? 'nav-tab-active' : ''; ?>">🔗 Internal Linking</a>
<a href="?page=finnish-network-global&tab=video_embedding" class="nav-tab <?php echo $active_tab == 'video_embedding' ? 'nav-tab-active' : ''; ?>">🎥 Video Embedding</a>
<a href="?page=finnish-network-global&tab=settings" class="nav-tab <?php echo $active_tab == 'settings' ? 'nav-tab-active' : ''; ?>">Settings</a>
<a href="?page=finnish-network-global&tab=logs" class="nav-tab <?php echo $active_tab == 'logs' ? 'nav-tab-active' : ''; ?>">Logs</a>
<a href="?page=finnish-network-global&tab=help" class="nav-tab <?php echo $active_tab == 'help' ? 'nav-tab-active' : ''; ?>">Help & FAQ</a>
</h2>
</div>
<div class="finnish-network-content">
<?php if ( $active_tab == 'dashboard' ) : ?>
<!-- DASHBOARD TAB -->
<?php
$current_view = isset( $_GET['view'] ) ? $_GET['view'] : 'overview';
?>
<div class="finnish-network-card" style="margin-bottom: 20px; padding: 10px;">
<ul class="subsubsub" style="float: none; margin: 0;">
<li class="all"><a href="?page=finnish-network-global&tab=dashboard&view=overview" class="<?php echo $current_view == 'overview' ? 'current' : ''; ?>">Overview</a> |</li>
<li class="publish"><a href="?page=finnish-network-global&tab=dashboard&view=incoming" class="<?php echo $current_view == 'incoming' ? 'current' : ''; ?>" style="color: #d63638;">Incoming (Staging)</a> |</li>
<li class="draft"><a href="?page=finnish-network-global&tab=dashboard&view=drafts" class="<?php echo $current_view == 'drafts' ? 'current' : ''; ?>">Drafts</a> |</li>
<li class="future"><a href="?page=finnish-network-global&tab=dashboard&view=scheduled" class="<?php echo $current_view == 'scheduled' ? 'current' : ''; ?>">Scheduled</a> |</li>
<li class="publish"><a href="?page=finnish-network-global&tab=dashboard&view=published" class="<?php echo $current_view == 'published' ? 'current' : ''; ?>">Published</a></li>
</ul>
<br class="clear">
</div>
<?php if ( $current_view == 'overview' ) : ?>
<div class="finnish-network-grid">
<div class="finnish-network-card">
<h2>System Status</h2>
<?php
$future_posts = wp_count_posts()->future;
$draft_posts = wp_count_posts()->draft;
$published_posts = wp_count_posts()->publish;
?>
<div style="display: flex; justify-content: space-around; text-align: center;">
<div>
<div class="finnish-network-stat"><?php echo $future_posts; ?></div>
<div class="finnish-network-stat-label">Scheduled</div>
</div>
<div>
<div class="finnish-network-stat" style="color: #d63638;"><?php echo wp_count_posts()->pending; ?></div>
<div class="finnish-network-stat-label">Incoming</div>
</div>
</div>
</div>
<div class="finnish-network-card">
<h2>Quick Actions</h2>
<form method="post" action="" style="display: inline;" class="finnish-fetch-form">
<input type="hidden" name="action" value="finnish_global_manual_fetch">
<?php wp_nonce_field( 'finnish_global_fetch_action', 'finnish_global_fetch_nonce' ); ?>
<?php submit_button( 'Fetch New Content', 'primary', 'submit', false ); ?>
</form>
<a href="?page=finnish-network-global&tab=dashboard&view=incoming" class="button">Go to Incoming</a>
</div>
</div>
<!-- NEW: Multi-Module Analytics Dashboard -->
<div class="finnish-network-card" style="margin-top: 20px;">
<h2>📈 Multi-Module Analytics</h2>
<?php
// Get today's date for queries
global $wpdb;
$today = current_time( 'Y-m-d' );
// RSS Feeds - total posts with _original_feed_id meta
$rss_today = $wpdb->get_var(
"SELECT COUNT(DISTINCT p.ID) FROM {$wpdb->posts} p
JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
WHERE pm.meta_key = '_original_feed_id'
AND p.post_status IN ('publish', 'future', 'draft')"
);
// URL Rewriter - total posts with _url_rewriter_source meta
$url_rewriter_today = $wpdb->get_var(
"SELECT COUNT(DISTINCT p.ID) FROM {$wpdb->posts} p
JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
WHERE pm.meta_key = '_url_rewriter_source'
AND p.post_status IN ('publish', 'future', 'draft')"
);
// Category Scraper - count today's processed URLs
$category_sources = get_option( 'category_scraper_processed_articles', array() );
$category_today = 0;
foreach ( $category_sources as $url => $data ) {
if ( isset( $data['processed_date'] ) && date( 'Y-m-d', strtotime( $data['processed_date'] ) ) === $today ) {
$category_today++;
}
}
// Evergreen Reviver - total posts updated by evergreen
$evergreen_today = $wpdb->get_var(
"SELECT COUNT(*) FROM {$wpdb->postmeta} pm
WHERE pm.meta_key = '_evergreen_last_updated'"
);
// URL Rewriter Queue Status
$url_queue = get_option( 'url_rewriter_queue', array() );
$url_queue_count = count( $url_queue );
?>
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; text-align: center;">
<div style="padding: 15px; border: 1px solid #ddd; border-radius: 8px;">
<div style="font-size: 1.8em; font-weight: bold; color: #0073aa;">📰 <?php echo $rss_today; ?></div>
<div style="font-size: 0.9em; color: #666;">RSS Posts</div>
<div style="font-size: 0.8em; color: #999;">Total</div>
</div>
<div style="padding: 15px; border: 1px solid #ddd; border-radius: 8px;">
<div style="font-size: 1.8em; font-weight: bold; color: <?php echo $url_queue_count > 0 ? '#d63638' : '#00a32a'; ?>;">
🔗 <?php echo $url_rewriter_today; ?>
</div>
<div style="font-size: 0.9em; color: #666;">URL Rewriter</div>
<div style="font-size: 0.8em; color: #999;"><?php echo $url_queue_count; ?> in queue</div>
</div>
<div style="padding: 15px; border: 1px solid #ddd; border-radius: 8px;">
<div style="font-size: 1.8em; font-weight: bold; color: #0073aa;">📂 <?php echo count($category_sources); ?></div>
<div style="font-size: 0.9em; color: #666;">Category Sources</div>
<div style="font-size: 0.8em; color: #999;">Configured</div>
</div>
<div style="padding: 15px; border: 1px solid #ddd; border-radius: 8px;">
<div style="font-size: 1.8em; font-weight: bold; color: #00a32a;">♻️ <?php echo $evergreen_today; ?></div>
<div style="font-size: 0.9em; color: #666;">Evergreen Posts</div>
<div style="font-size: 0.8em; color: #999;">Updated</div>
</div>
</div>
</div>
<!-- NEW: Content Performance Center -->
<div class="finnish-network-card" style="margin-top: 20px;">
<h2>📊 Content Performance Center</h2>
<?php
// Get performance analytics from existing post meta
global $wpdb;
// Content quality scores based on existing metrics
$total_posts = wp_count_posts()->publish;
// Posts with images
$posts_with_images = $wpdb->get_var(
"SELECT COUNT(DISTINCT p.ID) FROM {$wpdb->posts} p
JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
WHERE p.post_status = 'publish'
AND pm.meta_key = '_thumbnail_id'"
);
// Posts with SEO titles (Yoast/RankMath)
$posts_with_seo = $wpdb->get_var(
"SELECT COUNT(DISTINCT p.ID) FROM {$wpdb->posts} p
JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
WHERE p.post_status = 'publish'
AND (pm.meta_key = '_yoast_wpseo_title' OR pm.meta_key = 'rank_math_title')"
);
// Posts with internal links (approximate)
$posts_with_internal_links = $wpdb->get_var(
"SELECT COUNT(DISTINCT ID) FROM {$wpdb->posts}
WHERE post_status = 'publish'
AND post_content LIKE '%<a href%'"
);
// Calculate percentages
$image_percentage = $total_posts > 0 ? round(($posts_with_images / $total_posts) * 100) : 0;
$seo_percentage = $total_posts > 0 ? round(($posts_with_seo / $total_posts) * 100) : 0;
$linking_percentage = $total_posts > 0 ? round(($posts_with_internal_links / $total_posts) * 100) : 0;
// Average content length
$avg_length = $wpdb->get_var(
"SELECT AVG(CHAR_LENGTH(post_content)) FROM {$wpdb->posts}
WHERE post_status = 'publish' AND post_type = 'post'"
);
$avg_length = round($avg_length);
// Content freshness - posts from last 30 days
$recent_posts = $wpdb->get_var(
"SELECT COUNT(*) FROM {$wpdb->posts}
WHERE post_status = 'publish'
AND post_date > DATE_SUB(NOW(), INTERVAL 30 DAY)"
);
?>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-bottom: 20px;">
<div style="text-align: center; padding: 15px; border: 1px solid #ddd; border-radius: 8px;">
<div style="font-size: 2.2em; font-weight: bold; color: <?php echo $image_percentage >= 80 ? '#00a32a' : ($image_percentage >= 50 ? '#ffb900' : '#d63638'); ?>;">
<?php echo $image_percentage; ?>%
</div>
<div style="font-size: 0.9em; color: #666;">Image Coverage</div>
<div style="font-size: 0.8em; color: #999;"><?php echo $posts_with_images; ?>/<?php echo $total_posts; ?> posts</div>
</div>
<div style="text-align: center; padding: 15px; border: 1px solid #ddd; border-radius: 8px;">
<div style="font-size: 2.2em; font-weight: bold; color: <?php echo $seo_percentage >= 70 ? '#00a32a' : ($seo_percentage >= 40 ? '#ffb900' : '#d63638'); ?>;">
<?php echo $seo_percentage; ?>%
</div>
<div style="font-size: 0.9em; color: #666;">SEO Optimization</div>
<div style="font-size: 0.8em; color: #999;">Custom titles set</div>
</div>
<div style="text-align: center; padding: 15px; border: 1px solid #ddd; border-radius: 8px;">
<div style="font-size: 2.2em; font-weight: bold; color: <?php echo $linking_percentage >= 60 ? '#00a32a' : ($linking_percentage >= 30 ? '#ffb900' : '#d63638'); ?>;">
<?php echo $linking_percentage; ?>%
</div>
<div style="font-size: 0.9em; color: #666;">Internal Linking</div>
<div style="font-size: 0.8em; color: #999;">Posts with links</div>
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
<div style="padding: 15px; background: #f9f9f9; border-radius: 8px;">
<h4 style="margin: 0 0 10px 0; color: #0073aa;">📝 Content Metrics</h4>
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
<span>Average Length:</span>
<strong><?php echo number_format($avg_length); ?> chars</strong>
</div>
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
<span>Total Posts:</span>
<strong><?php echo number_format($total_posts); ?></strong>
</div>
<div style="display: flex; justify-content: space-between;">
<span>Recent (30 days):</span>
<strong><?php echo number_format($recent_posts); ?></strong>
</div>
</div>
<div style="padding: 15px; background: #f9f9f9; border-radius: 8px;">
<h4 style="margin: 0 0 10px 0; color: #0073aa;">🎯 Quality Score</h4>
<?php
$quality_score = round(($image_percentage + $seo_percentage + $linking_percentage) / 3);
$quality_color = $quality_score >= 70 ? '#00a32a' : ($quality_score >= 50 ? '#ffb900' : '#d63638');
$quality_status = $quality_score >= 70 ? 'Excellent' : ($quality_score >= 50 ? 'Good' : 'Needs Improvement');
?>
<div style="font-size: 3em; font-weight: bold; color: <?php echo $quality_color; ?>; text-align: center;">
<?php echo $quality_score; ?>
</div>
<div style="text-align: center; color: #666; font-weight: bold;">
<?php echo $quality_status; ?>
</div>
</div>
</div>
</div>
<!-- NEW: Competitive Analysis Dashboard -->
<div class="finnish-network-card" style="margin-top: 20px;">
<h2>🔍 Competitive Analysis Dashboard</h2>
<?php
// Analysis based on existing RSS feeds and category sources
global $wpdb;
// Get RSS feeds from automation_feed posts
$competitor_feeds = get_posts( array(
'post_type' => 'automation_feed',
'posts_per_page' => -1,
'meta_key' => '_feed_url'
));
// Get category scraper sources from options
$category_sources = get_option( 'category_scraper_sources', array() );
// Calculate competitor metrics
$total_competitors = count( $competitor_feeds ) + count( $category_sources );
// Posting frequency analysis (last 7 days)
$week_ago = date( 'Y-m-d', strtotime( '-7 days' ) );
$competitor_activity = array();
foreach ( $competitor_feeds as $feed ) {
$feed_url = get_post_meta( $feed->ID, '_feed_url', true );
$domain = parse_url( $feed_url, PHP_URL_HOST );
// Count posts from this feed in last 7 days
$recent_posts = $wpdb->get_var( $wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->posts} p
JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
WHERE pm.meta_key = '_original_feed_id'
AND pm.meta_value = %s
AND p.post_date >= %s",
$feed->ID,
$week_ago
));
if ( $recent_posts > 0 ) {
$competitor_activity[] = array(
'name' => $feed->post_title,
'domain' => $domain,
'posts' => $recent_posts,
'type' => 'RSS Feed'
);
}
}
// Add category scraper activity
foreach ( $category_sources as $source ) {
$domain = parse_url( $source['base_url'], PHP_URL_HOST );
$recent_scraped = 0;
// Check processed articles from this source in last 7 days
$processed_articles = get_option( 'category_scraper_processed_articles', array() );
foreach ( $processed_articles as $url => $data ) {
if ( strpos( $url, $domain ) !== false &&
isset( $data['processed_date'] ) &&
strtotime( $data['processed_date'] ) >= strtotime( $week_ago ) ) {
$recent_scraped++;
}
}
if ( $recent_scraped > 0 ) {
$competitor_activity[] = array(
'name' => $source['name'],
'domain' => $domain,
'posts' => $recent_scraped,
'type' => 'Category Source'
);
}
}
// Sort by activity level
usort( $competitor_activity, function( $a, $b ) {
return $b['posts'] - $a['posts'];
});
$active_competitors = count( $competitor_activity );
$total_competitor_posts = array_sum( array_column( $competitor_activity, 'posts' ) );
$avg_competitor_posts = $active_competitors > 0 ? round( $total_competitor_posts / $active_competitors, 1 ) : 0;
// Your site's activity for comparison
$your_recent_posts = $wpdb->get_var( $wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->posts}
WHERE post_status = 'publish'
AND post_date >= %s",
$week_ago
));
?>
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 20px;">
<div style="text-align: center; padding: 15px; border: 1px solid #ddd; border-radius: 8px;">
<div style="font-size: 2em; font-weight: bold; color: #0073aa;"><?php echo $total_competitors; ?></div>
<div style="font-size: 0.9em; color: #666;">Total Sources</div>
<div style="font-size: 0.8em; color: #999;">Monitored</div>
</div>
<div style="text-align: center; padding: 15px; border: 1px solid #ddd; border-radius: 8px;">
<div style="font-size: 2em; font-weight: bold; color: #00a32a;"><?php echo $active_competitors; ?></div>
<div style="font-size: 0.9em; color: #666;">Active Sources</div>
<div style="font-size: 0.8em; color: #999;">Last 7 days</div>
</div>
<div style="text-align: center; padding: 15px; border: 1px solid #ddd; border-radius: 8px;">
<div style="font-size: 2em; font-weight: bold; color: #d63638;"><?php echo $total_competitor_posts; ?></div>
<div style="font-size: 0.9em; color: #666;">Competitor Posts</div>
<div style="font-size: 0.8em; color: #999;">Weekly total</div>
</div>
<div style="text-align: center; padding: 15px; border: 1px solid #ddd; border-radius: 8px;">
<div style="font-size: 2em; font-weight: bold; color: <?php echo $your_recent_posts >= $avg_competitor_posts ? '#00a32a' : '#ffb900'; ?>;">
<?php echo $your_recent_posts; ?>
</div>
<div style="font-size: 0.9em; color: #666;">Your Posts</div>
<div style="font-size: 0.8em; color: #999;">vs avg <?php echo $avg_competitor_posts; ?></div>
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
<div style="padding: 15px; background: #f9f9f9; border-radius: 8px;">
<h4 style="margin: 0 0 15px 0; color: #0073aa;">📈 Top Active Sources</h4>
<?php if ( !empty( $competitor_activity ) ) : ?>
<?php foreach ( array_slice( $competitor_activity, 0, 5 ) as $competitor ) : ?>
<div style="display: flex; justify-content: space-between; margin-bottom: 8px; padding: 8px; background: white; border-radius: 4px;">
<div>
<strong><?php echo esc_html( substr( $competitor['name'], 0, 25 ) ); ?></strong>
<br><small style="color: #666;"><?php echo esc_html( $competitor['domain'] ); ?></small>
</div>
<div style="text-align: right;">
<strong style="color: #d63638;"><?php echo $competitor['posts']; ?></strong>
<br><small><?php echo $competitor['type']; ?></small>
</div>
</div>
<?php endforeach; ?>
<?php else : ?>
<div style="color: #666; font-style: italic;">No recent competitor activity detected.</div>
<?php endif; ?>
</div>
<div style="padding: 15px; background: #f9f9f9; border-radius: 8px;">
<h4 style="margin: 0 0 15px 0; color: #0073aa;">🎯 Content Gap Analysis</h4>
<?php
// Simple topic analysis based on recent competitor titles
$recent_competitor_posts = $wpdb->get_results( $wpdb->prepare(
"SELECT p.post_title, pm.meta_value as feed_id
FROM {$wpdb->posts} p
JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
WHERE pm.meta_key = '_original_feed_id'
AND p.post_date >= %s
ORDER BY p.post_date DESC
LIMIT 20",
$week_ago
));
$common_words = array();
foreach ( $recent_competitor_posts as $post ) {
$words = str_word_count( strtolower( $post->post_title ), 1 );
foreach ( $words as $word ) {
if ( strlen( $word ) > 4 ) { // Only longer words
$common_words[ $word ] = isset( $common_words[ $word ] ) ? $common_words[ $word ] + 1 : 1;
}
}
}
arsort( $common_words );
$top_topics = array_slice( $common_words, 0, 6, true );
?>
<?php if ( !empty( $top_topics ) ) : ?>
<div style="margin-bottom: 10px; color: #666;">Trending competitor topics:</div>
<?php foreach ( $top_topics as $topic => $count ) : ?>
<div style="display: inline-block; background: #0073aa; color: white; padding: 4px 8px; margin: 2px; border-radius: 12px; font-size: 0.8em;">
<?php echo esc_html( ucfirst( $topic ) ); ?> (<?php echo $count; ?>)
</div>
<?php endforeach; ?>
<?php else : ?>
<div style="color: #666; font-style: italic;">No trending topics identified yet.</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="finnish-network-card" style="margin-top: 20px;">
<h2>Recent Activity</h2>
<?php
$recent_args = array(
'post_type' => 'post',
'post_status' => 'any',
'posts_per_page' => 5,
'meta_query' => array( array( 'key' => '_original_feed_id', 'compare' => 'EXISTS' ) )
);
$recent_posts = get_posts( $recent_args );
if ( $recent_posts ) {
echo '<table class="wp-list-table widefat fixed striped">';
echo '<thead><tr><th>Title</th><th>Status</th><th>Date</th></tr></thead>';
echo '<tbody>';
foreach ( $recent_posts as $post ) {
echo '<tr>';
echo '<td><a href="' . get_edit_post_link( $post->ID ) . '">' . esc_html( $post->post_title ) . '</a></td>';
echo '<td>' . $post->post_status . '</td>';
echo '<td>' . get_the_date( 'Y-m-d H:i', $post->ID ) . '</td>';
echo '</tr>';
}
echo '</tbody></table>';
} else {
echo '<p>No recent activity.</p>';
}
?>
</div>
<?php elseif ( $current_view == 'incoming' ) : ?>
<!-- INCOMING VIEW -->
<div class="finnish-network-card">
<h2>Incoming Content (Staging)</h2>
<p>These are raw items fetched from RSS feeds. They have NOT been processed by AI yet.</p>
<form method="post" action="">
<input type="hidden" name="action" value="finnish_global_bulk_incoming_action">
<?php wp_nonce_field( 'finnish_global_bulk_incoming_action', 'finnish_global_bulk_incoming_nonce' ); ?>
<div class="tablenav top">
<div class="alignleft actions bulkactions">
<select name="bulk_action_selector">
<option value="-1">Bulk Actions</option>
<option value="generate">Generate Content (AI)</option>
<option value="delete">Delete Permanently</option>
</select>
<input type="submit" class="button action" value="Apply">
</div>
</div>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<td id="cb" class="manage-column column-cb check-column"><input type="checkbox" onclick="jQuery('.post-check').prop('checked', this.checked);"></td>
<th>Original Title</th>
<th>Source Feed</th>
<th>Date Fetched</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php
$args = array(
'post_type' => 'post',
'post_status' => 'pending',
'posts_per_page' => 50,
);
$pending_posts = get_posts( $args );
if ( $pending_posts ) {
foreach ( $pending_posts as $post ) {
$feed_id = get_post_meta( $post->ID, '_original_feed_id', true );
$feed_name = $feed_id ? get_the_title( $feed_id ) : 'Unknown';
?>
<tr>
<th scope="row" class="check-column"><input type="checkbox" name="post_ids[]" value="<?php echo $post->ID; ?>" class="post-check"></th>
<td>
<strong><?php echo esc_html( $post->post_title ); ?></strong>
<?php
$english_title = get_post_meta( $post->ID, '_original_title_en', true );
if ( $english_title ) {
echo '<br><span style="color: #666; font-style: italic;">EN: ' . esc_html( $english_title ) . '</span>';
}
?>
<br>
<a href="<?php echo esc_url( get_post_meta( $post->ID, '_original_rss_link', true ) ); ?>" target="_blank" style="font-size: 0.9em; color: #0073aa;">View Original Source</a>
</td>
<td><?php echo esc_html( $feed_name ); ?></td>
<td><?php echo get_the_date( 'Y-m-d H:i', $post->ID ); ?></td>
<td>
<a href="<?php echo wp_nonce_url( admin_url( 'admin.php?page=finnish-network-global&tab=dashboard&view=incoming&action=finnish_global_generate_content&post_id=' . $post->ID ), 'finnish_global_generate_content_' . $post->ID ); ?>" class="button button-primary finnish-generate-btn">Generate Content</a>
<a href="<?php echo wp_nonce_url( admin_url( 'admin.php?page=finnish-network-global&tab=dashboard&view=incoming&action=finnish_global_delete_content&post_id=' . $post->ID ), 'finnish_global_delete_content_' . $post->ID ); ?>" class="button button-link-delete" onclick="return confirm('Are you sure?')">Delete</a>
</td>
</tr>
<?php
}
} else {
echo '<tr><td colspan="5">No pending content. Click "Fetch New Content" in Overview to get more.</td></tr>';
}
?>
</tbody>
</table>
</form>
</div>
<?php elseif ( in_array( $current_view, array( 'drafts', 'scheduled', 'published' ) ) ) : ?>
<!-- LIST VIEWS -->
<div class="finnish-network-card">
<?php
$status_map = array( 'drafts' => 'draft', 'scheduled' => 'future', 'published' => 'publish' );
$wp_status = $status_map[$current_view];
echo '<h2>' . ucfirst( $current_view ) . ' Content</h2>';
$args = array(
'post_type' => 'post',
'post_status' => $wp_status,
'posts_per_page' => 20,
'meta_query' => array( array( 'key' => '_original_feed_id', 'compare' => 'EXISTS' ) )
);
$posts = get_posts( $args );
if ( $posts ) {
echo '<table class="wp-list-table widefat fixed striped">';
echo '<thead><tr><th width="60">Image</th><th>Title</th><th>Source</th><th>Date</th><th>Actions</th></tr></thead>';
echo '<tbody>';
foreach ( $posts as $post ) {
$thumb = get_the_post_thumbnail_url( $post->ID, 'thumbnail' );
$source_link = get_post_meta( $post->ID, '_original_rss_link', true );
$source_feed_id = get_post_meta( $post->ID, '_original_feed_id', true );
$source_name = $source_feed_id ? get_the_title( $source_feed_id ) : 'Unknown';
?>
<tr>
<td>
<?php if ( $thumb ) : ?>
<img src="<?php echo esc_url( $thumb ); ?>" style="width: 40px; height: 40px; object-fit: cover; border-radius: 4px;">
<?php else : ?>
<span class="dashicons dashicons-format-image" style="font-size: 24px; color: #ccc;"></span>
<?php endif; ?>
</td>
<td>
<strong><a href="<?php echo get_edit_post_link( $post->ID ); ?>"><?php echo esc_html( $post->post_title ); ?></a></strong>
</td>
<td>
<?php if ( $source_link ) : ?>
<a href="<?php echo esc_url( $source_link ); ?>" target="_blank" title="View Original Article">
<span class="dashicons dashicons-external"></span> <?php echo esc_html( $source_name ); ?>
</a>
<?php else : ?>
<span style="color: #ccc;">-</span>
<?php endif; ?>
</td>
<td><?php echo get_the_date( 'Y-m-d H:i', $post->ID ); ?></td>
<td>
<a href="<?php echo get_edit_post_link( $post->ID ); ?>" class="button button-small">Edit</a>
<a href="<?php echo wp_nonce_url( admin_url( 'admin.php?page=finnish-network-global&tab=dashboard&action=finnish_global_delete_content&post_id=' . $post->ID ), 'finnish_global_delete_content_' . $post->ID ); ?>" class="button button-small button-link-delete" onclick="return confirm('Are you sure?')">Delete</a>
</td>
</tr>
<?php
}
echo '</tbody></table>';
} else {
echo '<p>No items found.</p>';
}
?>
</div>
<?php endif; ?>
</div>
<?php elseif ( $active_tab == 'content' ) : ?>
<!-- CONTENT TAB -->
<div class="finnish-network-card">
<h2>Generated Content Library</h2>
<p>Review your AI-generated content, SEO details, and images.</p>
<div style="margin-bottom: 15px;">
<?php
$current_status = isset( $_GET['post_status'] ) ? $_GET['post_status'] : 'all';
$all_class = $current_status == 'all' ? 'current' : '';
$publish_class = $current_status == 'publish' ? 'current' : '';
$draft_class = $current_status == 'draft' ? 'current' : '';
?>
<ul class="subsubsub">
<li class="all"><a href="?page=finnish-network-global&tab=content&post_status=all" class="<?php echo $all_class; ?>">All</a> |</li>
<li class="publish"><a href="?page=finnish-network-global&tab=content&post_status=publish" class="<?php echo $publish_class; ?>">Published</a> |</li>
<li class="draft"><a href="?page=finnish-network-global&tab=content&post_status=draft" class="<?php echo $draft_class; ?>">Drafts</a></li>
</ul>
<br class="clear">
</div>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th width="80">Image</th>
<th width="20%">Generated Title</th>
<th width="20%">Original Title (English)</th>
<th>Meta Title</th>
<th>Meta Description</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php
$paged = isset( $_GET['paged'] ) ? max( 1, intval( $_GET['paged'] ) ) : 1;
$filter_status = isset( $_GET['post_status'] ) && in_array( $_GET['post_status'], array( 'publish', 'draft', 'future' ) ) ? $_GET['post_status'] : 'any';
$args = array(
'post_type' => 'post',
'post_status' => $filter_status,
'posts_per_page' => 20,
'paged' => $paged,
'meta_query' => array(
array(
'key' => '_original_feed_id',
'compare' => 'EXISTS',
),
),
);
$content_query = new WP_Query( $args );
if ( $content_query->have_posts() ) {
while ( $content_query->have_posts() ) {
$content_query->the_post();
$post_id = get_the_ID();
$thumb = get_the_post_thumbnail_url( $post_id, 'thumbnail' );
$keyword = get_post_meta( $post_id, '_primary_keyword', true );
$meta_desc = get_post_meta( $post_id, '_finnish_meta_description', true );
$meta_title = get_post_meta( $post_id, '_finnish_meta_title', true );
$original_title = get_post_meta( $post_id, '_original_title', true );
?>
<tr>
<td>
<?php if ( $thumb ) : ?>
<img src="<?php echo esc_url( $thumb ); ?>" style="width: 60px; height: 60px; object-fit: cover; border-radius: 4px; border: 1px solid #ddd;">
<?php else : ?>
<div style="width: 60px; height: 60px; background: #f0f0f1; display: flex; align-items: center; justify-content: center; border-radius: 4px;">
<span class="dashicons dashicons-format-image" style="color: #ccc;"></span>
</div>
<?php endif; ?>
</td>
<td>
<strong><a href="<?php echo get_edit_post_link( $post_id ); ?>"><?php echo get_the_title(); ?></a></strong>
<div class="row-actions">
<span class="edit"><a href="<?php echo get_edit_post_link( $post_id ); ?>">Edit</a> | </span>
<span class="view"><a href="<?php echo get_permalink( $post_id ); ?>" target="_blank">View</a> | </span>
<span class="trash"><a href="<?php echo wp_nonce_url( admin_url( 'admin.php?page=finnish-network-global&tab=content&action=finnish_global_delete_content&post_id=' . $post_id ), 'finnish_global_delete_content_' . $post_id ); ?>" onclick="return confirm('Are you sure?')">Delete</a></span>
</div>
</td>
<td>
<span style="color: #666; font-style: italic;"><?php echo esc_html( $original_title ? $original_title : 'N/A' ); ?></span>
</td>
<td>
<?php echo esc_html( $meta_title ? $meta_title : '-' ); ?>
</td>
<td>
<div style="font-size: 0.9em; color: #555; max-height: 60px; overflow: hidden;">
<?php echo esc_html( $meta_desc ? $meta_desc : 'No meta description generated.' ); ?>
</div>
</td>
<td>
<?php
$status = get_post_status();
$colors = array(
'publish' => '#00a32a',
'future' => '#0073aa',
'draft' => '#d63638',
);
$color = isset( $colors[$status] ) ? $colors[$status] : '#666';
echo '<span style="color: ' . $color . '; font-weight: bold; text-transform: capitalize;">' . $status . '</span>';
?>
</td>
<td>
<button type="button" class="button button-small" onclick="document.getElementById('change-image-<?php echo $post_id; ?>').style.display='block'">Change Image</button>
<div id="change-image-<?php echo $post_id; ?>" style="display:none; margin-top: 5px; background: #f9f9f9; padding: 10px; border: 1px solid #ddd; position: absolute; z-index: 100; right: 20px;">
<form method="post" action="">
<input type="hidden" name="action" value="finnish_global_change_image">
<input type="hidden" name="post_id" value="<?php echo $post_id; ?>">
<?php wp_nonce_field( 'finnish_global_change_image_action', 'finnish_global_change_image_nonce' ); ?>
<label>New Image Search Query:</label><br>
<input type="text" name="image_query" placeholder="e.g. technology" style="margin-bottom: 5px;"><br>
<submit_button class="button button-primary button-small">Find New Image</submit_button>
<button type="button" class="button button-small" onclick="document.getElementById('change-image-<?php echo $post_id; ?>').style.display='none'">Cancel</button>
</form>
</div>
</td>
</tr>
<?php
}
wp_reset_postdata();
} else {
echo '<tr><td colspan="6">No content generated yet.</td></tr>';
}
?>
</tbody>
</table>
<?php
// Simple Pagination
$total_pages = $content_query->max_num_pages;
if ( $total_pages > 1 ) {
echo '<div class="tablenav bottom"><div class="tablenav-pages">';
echo paginate_links( array(
'base' => add_query_arg( 'paged', '%#%' ),
'format' => '',
'prev_text' => '«',
'next_text' => '»',
'total' => $total_pages,
'current' => $paged
) );
echo '</div></div>';
}
?>
</div>
</div>
<?php elseif ( $active_tab == 'research' ) : ?>
<!-- RESEARCH TAB -->
<div class="finnish-network-card">
<h2>Keyword Research Tool</h2>
<p>Manually check keyword data using the DataForSEO API (Finland / Finnish).</p>
<form method="post" action="">
<table class="form-table">
<tr>
<th scope="row"><label>Seed Keyword</label></th>
<td>
<input type="text" name="research_keyword" class="regular-text" placeholder="e.g. nettikasino">
</td>
</tr>
</table>
<div style="margin-top: 10px;">
<input type="hidden" name="action" value="finnish_global_research_keyword">
<?php wp_nonce_field( 'finnish_global_research_action', 'finnish_global_research_nonce' ); ?>
<?php submit_button( 'Analyze Keyword', 'primary', 'submit', false ); ?>
</div>
</form>
<?php
if ( isset( $_POST['action'] ) && $_POST['action'] === 'finnish_global_research_keyword' ) {
if ( ! isset( $_POST['finnish_global_research_nonce'] ) || ! wp_verify_nonce( $_POST['finnish_global_research_nonce'], 'finnish_global_research_action' ) ) {
wp_die( 'Security check failed' );
}
$keyword = sanitize_text_field( $_POST['research_keyword'] );
if ( $keyword ) {
echo '<hr>';
echo '<h3>Results for: ' . esc_html( $keyword ) . '</h3>';
echo '<h3>Results for: ' . esc_html( $keyword ) . '</h3>';
$d4s = new Finnish_Global_DataForSEO_Service();
// Get Market Config
$market_code = get_option( 'finnish_global_target_market', 'fi_FI' );
$markets = array(
'fi_FI' => array( 'location_code' => 2246, 'lang_code' => 'fi' ),
'en_US' => array( 'location_code' => 2840, 'lang_code' => 'en' ),
'en_GB' => array( 'location_code' => 2826, 'lang_code' => 'en' ),
'sv_SE' => array( 'location_code' => 2752, 'lang_code' => 'sv' ),
'de_DE' => array( 'location_code' => 2276, 'lang_code' => 'de' ),
'es_ES' => array( 'location_code' => 2724, 'lang_code' => 'es' ),
'no_NO' => array( 'location_code' => 2578, 'lang_code' => 'no' ),
);
$config = isset( $markets[$market_code] ) ? $markets[$market_code] : $markets['fi_FI'];
$results = $d4s->research_keywords( $keyword, $config['lang_code'], $config['location_code'] );
if ( $results && ! is_wp_error( $results ) ) {
echo '<table class="wp-list-table widefat fixed striped">';
echo '<thead><tr><th>Keyword</th><th>Competition</th><th>Search Volume</th><th>CPC</th></tr></thead>';
echo '<tbody>';
foreach ( $results as $res ) {
echo '<tr>';
echo '<td>' . esc_html( $res['keyword'] ) . '</td>';
echo '<td>' . esc_html( $res['competition_level'] ) . '</td>';
echo '<td>' . esc_html( $res['search_volume'] ) . '</td>';
echo '<td>' . esc_html( $res['cpc'] ) . '</td>';
echo '</tr>';
}
echo '</tbody></table>';
} else {
echo '<div class="notice notice-error inline"><p>No results found or API error.</p></div>';
if ( is_wp_error( $results ) ) {
echo '<p>Error: ' . $results->get_error_message() . '</p>';
}
}
}
}
?>
</div>
<?php elseif ( $active_tab == 'feeds' ) : ?>
<!-- FEEDS TAB -->
<div class="finnish-network-card">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2>Managed RSS Feeds</h2>
<a href="<?php echo admin_url( 'post-new.php?post_type=automation_feed' ); ?>" class="button button-primary">Add New Feed</a>
</div>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>Feed Name</th>
<th>RSS URL</th>
<th>Last Updated</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php
$feeds = get_posts( array( 'post_type' => 'automation_feed', 'posts_per_page' => -1 ) );
if ( $feeds ) {
foreach ( $feeds as $feed ) {
$url = get_post_meta( $feed->ID, '_feed_url', true );
?>
<tr>
<td><strong><?php echo esc_html( $feed->post_title ); ?></strong></td>
<td><code><?php echo esc_html( $url ); ?></code></td>
<td><?php echo get_the_modified_date( 'Y-m-d H:i', $feed->ID ); ?></td>
<td>
<a href="<?php echo get_edit_post_link( $feed->ID ); ?>" class="button button-small">Edit</a>
<a href="<?php echo get_delete_post_link( $feed->ID ); ?>" class="button button-small button-link-delete" onclick="return confirm('Are you sure?')">Delete</a>
</td>
</tr>
<?php
}
} else {
echo '<tr><td colspan="4">No feeds configured yet. Click "Add New Feed" to start.</td></tr>';
}
?>
</tbody>
</table>
</div>
<?php elseif ( $active_tab == 'scheduler' ) : ?>
<!-- SCHEDULER TAB -->
<div class="finnish-network-grid">
<div class="finnish-network-card">
<h2>Auto-Scheduler Tool</h2>
<p>Use this tool to automatically fill your content calendar from your Drafts pool.</p>
<form method="post" action="">
<table class="form-table">
<tr>
<th scope="row"><label>Schedule Ahead</label></th>
<td>
<input type="number" name="days_ahead" value="30" min="1" max="90" class="small-text"> days
</form>
</div>
<!-- JIT List Removed (Merged into Upcoming Schedule below) -->
<!-- CALENDAR VIEW -->
<div class="finnish-network-card">
<h2>Content Calendar</h2>
<?php
$month = isset( $_GET['cal_month'] ) ? intval( $_GET['cal_month'] ) : date( 'n' );
$year = isset( $_GET['cal_year'] ) ? intval( $_GET['cal_year'] ) : date( 'Y' );
// Navigation
$prev_month = $month - 1;
$prev_year = $year;
if ( $prev_month < 1 ) { $prev_month = 12; $prev_year--; }
$next_month = $month + 1;
$next_year = $year;
if ( $next_month > 12 ) { $next_month = 1; $next_year++; }
// Ensure scheduler is instantiated
if ( ! isset( $scheduler ) ) $scheduler = new Finnish_Global_Scheduler();
$calendar_data = $scheduler->get_calendar_data( $month, $year );
$days_in_month = cal_days_in_month( CAL_GREGORIAN, $month, $year );
$first_day_timestamp = mktime( 0, 0, 0, $month, 1, $year );
$first_day_of_week = date( 'N', $first_day_timestamp ); // 1 (Mon) - 7 (Sun)
?>
<div class="finnish-calendar-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<a href="?page=finnish-network-global&tab=scheduler&cal_month=<?php echo $prev_month; ?>&cal_year=<?php echo $prev_year; ?>" class="button">« Prev</a>
<h3 style="margin: 0;"><?php echo date( 'F Y', $first_day_timestamp ); ?></h3>
<a href="?page=finnish-network-global&tab=scheduler&cal_month=<?php echo $next_month; ?>&cal_year=<?php echo $next_year; ?>" class="button">Next »</a>
</div>
<table class="finnish-calendar" style="width: 100%; border-collapse: collapse;">
<thead>
<tr>
<th style="padding: 10px; border: 1px solid #ddd; background: #f9f9f9;">Mon</th>
<th style="padding: 10px; border: 1px solid #ddd; background: #f9f9f9;">Tue</th>
<th style="padding: 10px; border: 1px solid #ddd; background: #f9f9f9;">Wed</th>
<th style="padding: 10px; border: 1px solid #ddd; background: #f9f9f9;">Thu</th>
<th style="padding: 10px; border: 1px solid #ddd; background: #f9f9f9;">Fri</th>
<th style="padding: 10px; border: 1px solid #ddd; background: #f9f9f9;">Sat</th>
<th style="padding: 10px; border: 1px solid #ddd; background: #f9f9f9;">Sun</th>
</tr>
</thead>
<tbody>
<tr>
<?php
// Empty cells before first day
for ( $i = 1; $i < $first_day_of_week; $i++ ) {
echo '<td style="padding: 10px; border: 1px solid #ddd; background: #fcfcfc;"></td>';
}
for ( $day = 1; $day <= $days_in_month; $day++ ) {
$data = isset( $calendar_data[$day] ) ? $calendar_data[$day] : array( 'posts' => array(), 'projected' => array() );
$has_content = !empty($data['posts']) || !empty($data['projected']);
$bg = $has_content ? '#f0f5ff' : '#fff';
$border = $has_content ? '2px solid #adc6ff' : '1px solid #ddd';
echo '<td style="padding: 10px; border: ' . $border . '; background: ' . $bg . '; height: 100px; vertical-align: top;">';
echo '<strong>' . $day . '</strong>';
// Show Actual Posts
if ( ! empty( $data['posts'] ) ) {
foreach ( $data['posts'] as $p ) {
$color = $p['status'] === 'future' ? '#1890ff' : '#52c41a'; // Blue for future, Green for published
$status_label = $p['status'] === 'future' ? 'Scheduled' : 'Published';
$module_source = isset( $p['module_source'] ) ? $p['module_source'] : 'RSS Scheduler';
$module_indicator = '';
// Add small module indicator
if ( $module_source === 'URL Rewriter' ) {
$module_indicator = '🔗';
} elseif ( $module_source === 'Category Scraper' ) {
$module_indicator = '📂';
} elseif ( $module_source === 'Evergreen Reviver' ) {
$module_indicator = '♻️';
} else {
$module_indicator = '📰';
}
echo '<div style="margin-top: 2px; background: ' . $color . '; color: #fff; padding: 2px 4px; border-radius: 2px; font-size: 0.75em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="' . esc_attr( $p['title'] . ' (' . $status_label . ' - ' . $module_source . ')' ) . '">';
echo $module_indicator . ' ' . esc_html( $p['time'] . ' ' . $p['title'] );
echo '</div>';
}
}
// Show Projected
if ( ! empty( $data['projected'] ) ) {
foreach ( $data['projected'] as $proj ) {
$time_str = !empty($proj['time']) ? '@ ' . $proj['time'] : '';
echo '<div style="margin-top: 2px; border: 1px dashed #faad14; color: #faad14; padding: 1px 3px; border-radius: 2px; font-size: 0.7em;">';
echo '<em>' . esc_html( $proj['count'] . 'x ' . $proj['name'] . ' ' . $time_str ) . '</em>';
echo '</div>';
}
}
echo '</td>';
if ( ( $day + $first_day_of_week - 1 ) % 7 == 0 ) {
echo '</tr><tr>';
}
}
// Empty cells after last day
$remaining = 7 - ( ( $days_in_month + $first_day_of_week - 1 ) % 7 );
if ( $remaining < 7 ) {
for ( $i = 0; $i < $remaining; $i++ ) {
echo '<td style="padding: 10px; border: 1px solid #ddd; background: #fcfcfc;"></td>';
}
}
?>
</tr>
</tbody>
</table>
</div>
<div class="finnish-network-card">
<h2>Manage Schedulers</h2>
<p>Create and manage your persistent auto-generation tasks.</p>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Status</th>
<th>Frequency</th>
<th>Settings</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php
if ( ! isset( $scheduler ) ) $scheduler = new Finnish_Global_Scheduler();
$schedulers = $scheduler->get_schedulers();
if ( ! empty( $schedulers ) ) {
foreach ( $schedulers as $s ) {
$status_color = $s['status'] === 'active' ? 'green' : 'orange';
$feed_name = !empty($s['settings']['feed_id']) ? get_the_title($s['settings']['feed_id']) : 'All Feeds';
$posts = $s['settings']['posts_per_day'];
// Scheduler Type (what kind of content generation)
$scheduler_type = isset( $s['settings']['type'] ) ? $s['settings']['type'] : 'recurring';
$type_labels = array(
'recurring' => 'Content (RSS)',
'evergreen' => 'Evergreen Reviver',
'url_rewriter' => 'URL Rewriter',
'category_scraper' => 'Category Scraper'
);
$scheduler_type_display = isset( $type_labels[$scheduler_type] ) ? $type_labels[$scheduler_type] : ucfirst( $scheduler_type );
// Frequency display (how often it runs)
$frequency_display = 'Daily (Recurring)';
$interval_days = isset( $s['settings']['interval_days'] ) ? $s['settings']['interval_days'] : 1;
// For new modules, check schedule_type; for old modules, check type
$schedule_check_type = isset( $s['settings']['schedule_type'] ) ? $s['settings']['schedule_type'] : $s['settings']['type'];
if ( $schedule_check_type === 'range' ) {
$frequency_display = 'Range: ' . $s['settings']['start_date'] . ' to ' . $s['settings']['end_date'];
} elseif ( $schedule_check_type === 'specific' ) {
$count = count( $s['settings']['specific_dates'] );
$frequency_display = 'Specific Dates (' . $count . ')';
} else {
// Recurring - show interval
if ( $interval_days == 1 ) {
$frequency_display = 'Daily (Recurring)';
} else {
$frequency_display = "Every {$interval_days} Days (Recurring)";
}
}
?>
<tr>
<td><strong><?php echo esc_html( $s['name'] ); ?></strong></td>
<td><span style="background: #f0f8ff; padding: 2px 8px; border-radius: 3px; font-size: 11px; font-weight: bold;"><?php echo esc_html( $scheduler_type_display ); ?></span></td>
<td><span style="color: <?php echo $status_color; ?>; font-weight: bold; text-transform: uppercase;"><?php echo esc_html( $s['status'] ); ?></span></td>
<td><?php echo esc_html( $frequency_display ); ?></td>
<td>
<?php echo "$posts posts from $feed_name"; ?>
</td>
<td>
<a href="<?php echo admin_url( 'admin.php?page=finnish-network-global&tab=scheduler&action=finnish_global_scheduler_edit&id=' . $s['id'] ); ?>" class="button button-small">Edit</a>
<?php if ( $s['status'] === 'active' ) : ?>
<a href="<?php echo wp_nonce_url( admin_url( 'admin.php?page=finnish-network-global&tab=scheduler&action=pause_scheduler&id=' . $s['id'] ), 'finnish_global_scheduler_action' ); ?>" class="button button-small">Pause</a>
<?php else : ?>
<a href="<?php echo wp_nonce_url( admin_url( 'admin.php?page=finnish-network-global&tab=scheduler&action=resume_scheduler&id=' . $s['id'] ), 'finnish_global_scheduler_action' ); ?>" class="button button-small">Resume</a>
<?php endif; ?>
<a href="<?php echo wp_nonce_url( admin_url( 'admin.php?page=finnish-network-global&tab=scheduler&action=run_now_scheduler&id=' . $s['id'] ), 'finnish_global_scheduler_action' ); ?>" class="button button-small button-primary">Run Now</a>
<a href="<?php echo wp_nonce_url( admin_url( 'admin.php?page=finnish-network-global&tab=scheduler&action=delete_scheduler&id=' . $s['id'] ), 'finnish_global_scheduler_action' ); ?>" class="button button-small button-link-delete" onclick="return confirm('Delete this scheduler permanently?')">Delete</a>
</td>
</tr>
<?php
}
} else {
echo '<tr><td colspan="5">No schedulers created yet.</td></tr>';
}
?>
</tbody>
</table>
</div>
</div>
<div class="finnish-network-card">
<?php
$edit_mode = false;
$edit_data = null;
if ( isset( $_GET['action'] ) && $_GET['action'] === 'finnish_global_scheduler_edit' && isset( $_GET['id'] ) ) {
$edit_mode = true;
if ( ! isset( $scheduler ) ) $scheduler = new Finnish_Global_Scheduler();
$edit_data = $scheduler->get_scheduler( $_GET['id'] );
}
$s_type = $edit_mode ? $edit_data['settings']['type'] : 'recurring';
// For edit mode, determine content source and schedule type
if ( $edit_mode ) {
$stored_type = $edit_data['settings']['type'];
$stored_schedule_type = isset( $edit_data['settings']['schedule_type'] ) ? $edit_data['settings']['schedule_type'] : 'recurring';
if ( in_array( $stored_type, array( 'evergreen', 'url_rewriter', 'category_scraper' ) ) ) {
$content_source = $stored_type;
$schedule_type = $stored_schedule_type;
} else {
$content_source = 'rss_feeds';
$schedule_type = $stored_type;
}
} else {
$content_source = 'rss_feeds';
$schedule_type = 'recurring';
}
?>
<h2><?php echo $edit_mode ? 'Edit Scheduler' : 'Create New Scheduler'; ?></h2>
<p><?php echo $edit_mode ? 'Update your scheduler settings below.' : 'Set up a recurring daily task, a date range, or specific dates.'; ?></p>
<form method="post" action="">
<?php if ( $edit_mode ) : ?>
<input type="hidden" name="scheduler_id" value="<?php echo esc_attr( $edit_data['id'] ); ?>">
<?php endif; ?>
<table class="form-table">
<tr>
<th scope="row">Scheduler Name</th>
<td>
<input type="text" name="scheduler_name" class="regular-text" placeholder="e.g. Daily Tech News" value="<?php echo $edit_mode ? esc_attr( $edit_data['name'] ) : ''; ?>" required>
</td>
</tr>
<tr>
<th scope="row">Content Source</th>
<td>
<select name="content_source" id="content_source" onchange="toggleSchedulerFields()">
<option value="rss_feeds" <?php selected( $content_source, 'rss_feeds' ); ?>>RSS Feeds</option>
<option value="evergreen" <?php selected( $content_source, 'evergreen' ); ?>>Evergreen Reviver (Rewrite Old Posts)</option>
<option value="url_rewriter" <?php selected( $content_source, 'url_rewriter' ); ?>>URL Rewriter (Process URL Queue)</option>
<option value="category_scraper" <?php selected( $content_source, 'category_scraper' ); ?>>Category Scraper (Auto-discover Articles)</option>
</select>
<p class="description">What type of content should this scheduler process?</p>
</td>
</tr>
<tr id="row_category_sources" style="display:none;">
<th scope="row">Category Sources</th>
<td>
<?php
// Get available category sources
if ( class_exists( 'Finnish_Global_Category_Scraper_Module' ) ) {
$category_scraper = new Finnish_Global_Category_Scraper_Module();
$available_sources = $category_scraper->get_sources();
// Get selected sources for edit mode
$selected_sources = array();
if ( $edit_mode && isset( $edit_data['settings']['category_sources'] ) ) {
$selected_sources = $edit_data['settings']['category_sources'];
}
if ( ! empty( $available_sources ) ) {
echo '<div style="max-height: 150px; overflow-y: auto; border: 1px solid #ddd; padding: 10px; background: #f9f9f9;">';
foreach ( $available_sources as $source ) {
$source_name = isset( $source['name'] ) ? $source['name'] : 'Unknown';
$source_url = isset( $source['url'] ) ? $source['url'] : '';
$source_id = isset( $source['id'] ) ? $source['id'] : md5( $source_url );
$checked = in_array( $source_id, $selected_sources ) ? 'checked' : '';
echo '<label style="display: block; margin-bottom: 5px;">';
echo '<input type="checkbox" name="category_sources[]" value="' . esc_attr( $source_id ) . '" ' . $checked . '> ';
echo '<strong>' . esc_html( $source_name ) . '</strong><br>';
echo '<small style="color: #666;">' . esc_html( $source_url ) . '</small>';
echo '</label>';
}
echo '</div>';
} else {
echo '<p style="color: #d63638;"><strong>No category sources configured!</strong> Please add sources in the <a href="?page=finnish-network-global&tab=category_scraper">Category Scraper tab</a> first.</p>';
}
} else {
echo '<p style="color: #d63638;">Category Scraper module not available.</p>';
}
?>
<p class="description">Select which category sources this scheduler should process. Leave empty to process all sources.</p>
</td>
</tr>
<tr id="row_url_rewriter_source" style="display:none;">
<th scope="row">URL Source Filter</th>
<td>
<?php
$selected_url_sources = array();
if ( $edit_mode && isset( $edit_data['settings']['url_source_filters'] ) ) {
$selected_url_sources = $edit_data['settings']['url_source_filters'];
} elseif ( $edit_mode && isset( $edit_data['settings']['url_source_filter'] ) ) {
// Backward compatibility for single filter
$selected_url_sources = array( $edit_data['settings']['url_source_filter'] );
}
// Default to all sources if none selected
if ( empty( $selected_url_sources ) ) {
$selected_url_sources = array( 'manual', 'bulk', 'sitemap' );
}
?>
<label><input type="checkbox" name="url_source_filters[]" value="manual" <?php echo in_array( 'manual', $selected_url_sources ) ? 'checked' : ''; ?> /> Manual URLs</label><br>
<label><input type="checkbox" name="url_source_filters[]" value="bulk" <?php echo in_array( 'bulk', $selected_url_sources ) ? 'checked' : ''; ?> /> Bulk Import URLs</label><br>
<label><input type="checkbox" name="url_source_filters[]" value="sitemap" <?php echo in_array( 'sitemap', $selected_url_sources ) ? 'checked' : ''; ?> /> Sitemap URLs</label>
<p class="description">Select which types of URLs this scheduler should process from the URL queue. Select multiple sources as needed.</p>
</td>
</tr>
<tr>
<th scope="row">Schedule Type</th>
<td>
<select name="scheduler_type" id="scheduler_type" onchange="toggleSchedulerFields()">
<option value="recurring" <?php selected( $schedule_type, 'recurring' ); ?>>Recurring (Daily Forever)</option>
<option value="range" <?php selected( $schedule_type, 'range' ); ?>>Date Range</option>
<option value="specific" <?php selected( $schedule_type, 'specific' ); ?>>Specific Dates</option>
</select>
<p class="description">How often should this scheduler run?</p>
</td>
</tr>
<tr id="row_range" style="display:none;">
<th scope="row">Date Range</th>
<td>
From <input type="date" name="start_date" value="<?php echo $edit_mode && isset($edit_data['settings']['start_date']) ? esc_attr($edit_data['settings']['start_date']) : ''; ?>">
to <input type="date" name="end_date" value="<?php echo $edit_mode && isset($edit_data['settings']['end_date']) ? esc_attr($edit_data['settings']['end_date']) : ''; ?>">
</td>
</tr>
<tr id="row_specific" style="display:none;">
<th scope="row">Specific Dates</th>
<td>
<textarea name="specific_dates" rows="3" class="large-text" placeholder="YYYY-MM-DD (one per line)"><?php
if ( $edit_mode && !empty($edit_data['settings']['specific_dates']) ) {
echo esc_textarea( implode("\n", $edit_data['settings']['specific_dates']) );
}
?></textarea>
<p class="description">Enter dates in YYYY-MM-DD format, one per line.</p>
</td>
</tr>
<tr>
<th scope="row">Posts Per Day</th>
<td>
<input type="number" name="posts_per_day" value="<?php echo $edit_mode ? intval( $edit_data['settings']['posts_per_day'] ) : '3'; ?>" min="1" max="20" class="small-text">
</td>
</tr>
<tr>
<th scope="row">Source Feed</th>
<td>
<select name="feed_id">
<option value="">All Feeds (Mix)</option>
<?php
$feeds = get_posts( array( 'post_type' => 'automation_feed', 'posts_per_page' => -1 ) );
$current_feed = $edit_mode ? $edit_data['settings']['feed_id'] : '';
foreach ( $feeds as $feed ) {
echo '<option value="' . $feed->ID . '" ' . selected( $current_feed, $feed->ID, false ) . '>' . esc_html( $feed->post_title ) . '</option>';
}
?>
</select>
</td>
</tr>
<tr>
<th scope="row">Time Strategy</th>
<td>
<?php $strategy = $edit_mode ? $edit_data['settings']['time_strategy'] : 'random'; ?>
<select name="time_strategy" onchange="document.getElementById('specific_time_row_new').style.display = this.value === 'specific' ? 'table-row' : 'none';">
<option value="random" <?php selected( $strategy, 'random' ); ?>>Random Spread (09:00 - 21:00)</option>
<option value="specific" <?php selected( $strategy, 'specific' ); ?>>Specific Time</option>
</select>
</td>
</tr>
<tr id="specific_time_row_new" style="display:<?php echo $strategy === 'specific' ? 'table-row' : 'none'; ?>;">
<th scope="row">Specific Time</th>
<td>
<input type="time" name="specific_time" value="<?php echo $edit_mode ? esc_attr( $edit_data['settings']['specific_time'] ) : ''; ?>">
</td>
</tr>
<tr>
<th scope="row">Post Every X Days</th>
<td>
<?php $interval_days = $edit_mode && isset($edit_data['settings']['interval_days']) ? $edit_data['settings']['interval_days'] : 1; ?>
<select name="interval_days">
<option value="1" <?php selected( $interval_days, 1 ); ?>>Every Day (Daily)</option>
<option value="2" <?php selected( $interval_days, 2 ); ?>>Every 2 Days</option>
<option value="3" <?php selected( $interval_days, 3 ); ?>>Every 3 Days</option>
<option value="7" <?php selected( $interval_days, 7 ); ?>>Every 7 Days (Weekly)</option>
<option value="14" <?php selected( $interval_days, 14 ); ?>>Every 14 Days (Bi-weekly)</option>
<option value="30" <?php selected( $interval_days, 30 ); ?>>Every 30 Days (Monthly)</option>
</select>
<p class="description">How often should this scheduler run? Default is daily.</p>
</td>
</tr>
<tr>
<th scope="row">Category</th>
<td>
<?php $cat_id = $edit_mode ? $edit_data['settings']['category_id'] : ''; ?>
<select name="category_id">
<option value="">-- Auto-Detect (AI) --</option>
<?php
$categories = get_categories( array( 'hide_empty' => false ) );
foreach ( $categories as $cat ) {
echo '<option value="' . $cat->term_id . '" ' . selected( $cat_id, $cat->term_id, false ) . '>' . esc_html( $cat->name ) . '</option>';
}
?>
</select>
<br>
<label><input type="checkbox" name="ai_category" value="1" <?php checked( $edit_mode ? $edit_data['settings']['ai_category'] : true ); ?>> Use AI to match category if "Auto-Detect" is selected.</label>
</td>
</tr>
<tr id="row_evergreen" style="display:none;">
<th scope="row">Minimum Post Age</th>
<td>
<?php $min_age = $edit_mode && isset($edit_data['settings']['min_post_age']) ? $edit_data['settings']['min_post_age'] : 180; ?>
<select name="min_post_age">
<option value="30" <?php selected( $min_age, 30 ); ?>>30 days (1 month)</option>
<option value="90" <?php selected( $min_age, 90 ); ?>>90 days (3 months)</option>
<option value="180" <?php selected( $min_age, 180 ); ?>>180 days (6 months)</option>
<option value="365" <?php selected( $min_age, 365 ); ?>>365 days (1 year)</option>
</select>
<p class="description">Only rewrite posts older than this age.</p>
<br>
<label>
<input type="checkbox" name="finnish_global_enable_evergreen_slack" value="1" <?php checked( get_option( 'finnish_global_enable_evergreen_slack', false ) ); ?>>
Send Slack notifications for Evergreen updates
</label>
</td>
</tr>
<tr>
<th scope="row">Post Author</th>
<td>
<?php $author_id = $edit_mode && isset($edit_data['settings']['author_id']) ? $edit_data['settings']['author_id'] : 0; ?>
<select name="author_id">
<option value="0">-- Random/Current Author --</option>
<?php
$users = get_users( array( 'orderby' => 'display_name' ) );
foreach ( $users as $user ) {
echo '<option value="' . $user->ID . '" ' . selected( $author_id, $user->ID, false ) . '>' . esc_html( $user->display_name ) . '</option>';
}
?>
</select>
<p class="description">Assign a specific author to generated posts. Leave as "Random" to keep existing authors.</p>
</td>
</tr>
<tr id="row_fetch_fresh">
<th scope="row">Fetch Fresh Content?</th>
<td>
<?php $fetch_fresh = $edit_mode && isset($edit_data['settings']['fetch_fresh']) ? $edit_data['settings']['fetch_fresh'] : false; ?>
<label><input type="checkbox" name="fetch_fresh" value="1" <?php checked( $fetch_fresh ); ?>> Yes, fetch new articles from feed(s) before scheduling.</label>
<p class="description">If checked, system will fetch new items and <strong>automatically rewrite them with AI</strong> before scheduling.</p>
</td>
</tr>
<tr>
<th scope="row">Perplexity AI Enhancement</th>
<td>
<?php
$perplexity_facts = true; // Default enabled
$perplexity_links = true; // Default enabled
if ( $edit_mode && isset($edit_data['settings']['perplexity_facts']) ) {
$perplexity_facts = (bool) $edit_data['settings']['perplexity_facts'];
}
if ( $edit_mode && isset($edit_data['settings']['perplexity_links']) ) {
$perplexity_links = (bool) $edit_data['settings']['perplexity_links'];
}
?>
<label style="display: block; margin-bottom: 8px;">
<input type="checkbox" name="perplexity_facts" value="1" <?php checked( $perplexity_facts ); ?>>
Enable Perplexity Facts Enhancement
</label>
<label style="display: block;">
<input type="checkbox" name="perplexity_links" value="1" <?php checked( $perplexity_links ); ?>>
Enable Perplexity Authority Links
</label>
<p class="description">Control whether this scheduler uses Perplexity AI for facts enhancement and authority link insertion. These settings override global Perplexity settings for this specific scheduler.</p>
</td>
</tr>
<tr>
<th scope="row">Writing Style Mixer (Optional)</th>
<td>
<?php $scheduler_style = $edit_mode && isset($edit_data['settings']['scheduler_style_mixer']) ? $edit_data['settings']['scheduler_style_mixer'] : ''; ?>
<select name="scheduler_style_mixer" id="scheduler_style_mixer">
<option value="">-- Default Style --</option>
<option value="breaking_news" <?php selected( $scheduler_style, 'breaking_news' ); ?>>Breaking News - Urgent tone, short paragraphs, active voice</option>
<option value="opinion_editorial" <?php selected( $scheduler_style, 'opinion_editorial' ); ?>>Opinion Editorial - Personal voice, takes clear stances</option>
<option value="technical_guide" <?php selected( $scheduler_style, 'technical_guide' ); ?>>Technical Guide - Expert tone, step-by-step approach</option>
<option value="listicle_format" <?php selected( $scheduler_style, 'listicle_format' ); ?>>Listicle Format - Numbered points, scannable structure</option>
<option value="deep_analysis" <?php selected( $scheduler_style, 'deep_analysis' ); ?>>Deep Analysis - Research-heavy, long-form exploration</option>
<option value="conversational_blog" <?php selected( $scheduler_style, 'conversational_blog' ); ?>>Conversational Blog - Friendly, casual tone with contractions</option>
<option value="investigative_report" <?php selected( $scheduler_style, 'investigative_report' ); ?>>Investigative Report - Fact-based, neutral, multiple sources</option>
<option value="supportive_companion" <?php selected( $scheduler_style, 'supportive_companion' ); ?>>Supportive Companion - Empathetic tone for mental health content</option>
<option value="personal_journey" <?php selected( $scheduler_style, 'personal_journey' ); ?>>Personal Journey - Vulnerable sharing with personal anecdotes</option>
<option value="gentle_mentor" <?php selected( $scheduler_style, 'gentle_mentor' ); ?>>Gentle Mentor - Nurturing, non-judgmental advice</option>
<option value="witty_observer" <?php selected( $scheduler_style, 'witty_observer' ); ?>>Witty Observer - Clever, engaging sarcasm</option>
<option value="dry_humor" <?php selected( $scheduler_style, 'dry_humor' ); ?>>Dry Humor - Deadpan comedy, British-style wit</option>
<option value="playful_storyteller" <?php selected( $scheduler_style, 'playful_storyteller' ); ?>>Playful Storyteller - Light-hearted with funny examples</option>
</select>
<p class="description">Override global Style Mixer for this scheduler only. Leave as "Default Style" to use global settings or no styling.</p>
</td>
</tr>
<tr>
<th scope="row">Quotable Content Enhancement</th>
<td>
<?php $quotable_enabled = $edit_mode && isset($edit_data['settings']['quotable_content_enabled']) ? $edit_data['settings']['quotable_content_enabled'] : false; ?>
<label>
<input type="checkbox" name="quotable_content_enabled" value="1" <?php checked( $quotable_enabled ); ?>>
Enable quotable content excerpts
</label>
<p class="description">Add 1-2 compelling quotes or key insights as highlighted blockquotes when contextually appropriate. Integrated into main content generation (no additional API costs).</p>
</td>
</tr>
<tr>
<th scope="row">Content Structure</th>
<td>
<?php $content_structure = $edit_mode && isset($edit_data['settings']['content_structure']) ? $edit_data['settings']['content_structure'] : 'strict'; ?>
<select name="content_structure" id="content_structure">
<option value="strict" <?php selected( $content_structure, 'strict' ); ?>>Strict SEO (Recommended) - Fixed heading structure, guaranteed H2 sections</option>
<option value="natural" <?php selected( $content_structure, 'natural' ); ?>>Natural Flow - More organic structure, less rigid formatting</option>
</select>
<p class="description"><strong>Strict SEO:</strong> Uses current proven structure with guaranteed H2 headings and consistent formatting. <strong>Natural Flow:</strong> Allows more organic article structure but may vary in SEO consistency. Affects both RSS and all modules.</p>
</td>
</tr>
</table>
<div style="margin-top: 20px;">
<input type="hidden" name="action" value="finnish_global_create_scheduler">
<?php wp_nonce_field( 'finnish_global_create_scheduler_action', 'finnish_global_create_scheduler_nonce' ); ?>
<?php submit_button( $edit_mode ? 'Update Scheduler' : 'Create Scheduler', 'primary', 'submit', false ); ?>
<?php if ( $edit_mode ) : ?>
<a href="<?php echo admin_url( 'admin.php?page=finnish-network-global&tab=scheduler' ); ?>" class="button">Cancel</a>
<?php endif; ?>
</div>
</form>
<script>
function toggleSchedulerFields() {
var contentSource = document.getElementById('content_source').value;
var scheduleType = document.getElementById('scheduler_type').value;
// Show/hide schedule type options
document.getElementById('row_range').style.display = scheduleType === 'range' ? 'table-row' : 'none';
document.getElementById('row_specific').style.display = scheduleType === 'specific' ? 'table-row' : 'none';
document.getElementById('row_evergreen').style.display = contentSource === 'evergreen' ? 'table-row' : 'none';
// Show/hide category sources selection
document.getElementById('row_category_sources').style.display = contentSource === 'category_scraper' ? 'table-row' : 'none';
// Show/hide URL rewriter source filter (NEW FEATURE)
document.getElementById('row_url_rewriter_source').style.display = contentSource === 'url_rewriter' ? 'table-row' : 'none';
// Hide fetch fresh content for URL Rewriter and Category Scraper
var hideFetchFresh = (contentSource === 'url_rewriter' || contentSource === 'category_scraper');
document.getElementById('row_fetch_fresh').style.display = hideFetchFresh ? 'none' : 'table-row';
// Update feed selector visibility
var showFeeds = (contentSource === 'rss_feeds');
var feedSelects = document.querySelectorAll('select[name="feed_id"]');
feedSelects.forEach(function(select) {
var feedRow = select.closest('tr');
if (feedRow) {
feedRow.style.display = showFeeds ? 'table-row' : 'none';
}
});
}
// Run on load
toggleSchedulerFields();
</script>
</div>
<div class="finnish-network-card">
<h2>Upcoming Schedule</h2>
<ul>
<?php
// 1. Get Future Posts (Enhanced to show module sources)
$future_posts = get_posts( array(
'post_status' => 'future',
'posts_per_page' => 10,
'orderby' => 'date',
'order' => 'ASC'
) );
$items = array();
if ( $future_posts ) {
foreach ( $future_posts as $p ) {
// Determine which module created this post
$module_source = '';
if ( get_post_meta( $p->ID, '_url_rewriter_source', true ) ) {
$module_source = 'URL Rewriter';
$module_color = '#0073aa';
$module_icon = 'dashicons-admin-links';
} elseif ( get_post_meta( $p->ID, '_category_scraper_source', true ) ) {
$module_source = 'Category Scraper';
$module_color = '#00a32a';
$module_icon = 'dashicons-category';
} elseif ( get_post_meta( $p->ID, '_evergreen_last_updated', true ) ) {
$module_source = 'Evergreen Reviver';
$module_color = '#d63638';
$module_icon = 'dashicons-update';
} else {
$module_source = 'RSS Scheduler';
$module_color = '#666';
$module_icon = 'dashicons-rss';
}
$items[] = array(
'type' => 'post',
'date' => $p->post_date,
'title' => $p->post_title,
'id' => $p->ID,
'module_source' => $module_source,
'module_color' => $module_color,
'module_icon' => $module_icon
);
}
}
// 2. Get JIT Events (Legacy) & Persistent Scheduler Projections
// $scheduler is instantiated above in the Calendar section
if ( ! isset( $scheduler ) ) $scheduler = new Finnish_Global_Scheduler();
// Legacy JIT
$jit_events = $scheduler->get_upcoming_jit_events( 10 );
if ( $jit_events ) {
foreach ( $jit_events as $e ) {
$feed_name = $e['feed_id'] ? get_the_title( $e['feed_id'] ) : 'All Feeds';
$items[] = array(
'type' => 'jit',
'date' => $e['date'],
'count' => $e['count'],
'feed' => $feed_name,
'feed_id' => $e['feed_id']
);
}
}
// Persistent Schedulers
$proj_events = $scheduler->get_projected_scheduler_events( 10 );
if ( $proj_events ) {
foreach ( $proj_events as $e ) {
// Determine source based on scheduler type
$scheduler_type = isset( $e['scheduler_type'] ) ? $e['scheduler_type'] : 'recurring';
if ( $scheduler_type === 'url_rewriter' ) {
$feed_name = 'URL Queue';
} elseif ( $scheduler_type === 'category_scraper' ) {
$feed_name = 'Website Categories';
} elseif ( $scheduler_type === 'evergreen' ) {
$feed_name = 'Old Posts Revival';
} else {
$feed_name = $e['feed_id'] ? get_the_title( $e['feed_id'] ) : 'All Feeds';
}
$items[] = array(
'type' => 'scheduler',
'date' => $e['date'],
'count' => $e['count'],
'feed' => $feed_name,
'name' => $e['name']
);
}
}
// 3. Sort by Date
usort( $items, function($a, $b) {
return strtotime($a['date']) - strtotime($b['date']);
} );
// 4. Display (Limit to top 10)
$items = array_slice( $items, 0, 10 );
if ( ! empty( $items ) ) {
foreach ( $items as $item ) {
$date_display = date( 'M j, H:i', strtotime( $item['date'] ) );
if ( $item['type'] === 'post' ) {
$module_label = isset( $item['module_source'] ) ? '<span style="color: ' . $item['module_color'] . '; font-weight: bold; font-size: 11px;">[' . $item['module_source'] . ']</span>' : '';
$module_icon = isset( $item['module_icon'] ) ? $item['module_icon'] : 'dashicons-admin-post';
$icon_color = isset( $item['module_color'] ) ? $item['module_color'] : '#0073aa';
echo '<li><strong>' . $date_display . '</strong>: <span class="dashicons ' . $module_icon . '" style="color:' . $icon_color . ';"></span> <a href="' . get_edit_post_link( $item['id'] ) . '">' . esc_html( $item['title'] ) . '</a> ' . $module_label . '</li>';
} elseif ( $item['type'] === 'jit' ) {
echo '<li><strong>' . $date_display . '</strong>: <span class="dashicons dashicons-update" style="color:#d63638;"></span> Auto-Generate <strong>' . intval( $item['count'] ) . '</strong> posts from <em>' . esc_html( $item['feed'] ) . '</em></li>';
} elseif ( $item['type'] === 'scheduler' ) {
echo '<li><strong>' . $date_display . '</strong>: <span class="dashicons dashicons-calendar-alt" style="color:#46b450;"></span> <strong>' . esc_html( $item['name'] ) . '</strong>: Auto-Generate <strong>' . intval( $item['count'] ) . '</strong> posts from <em>' . esc_html( $item['feed'] ) . '</em></li>';
}
}
echo '<li style="margin-top:10px; border-top:1px solid #eee; padding-top:5px;">... <a href="' . admin_url( 'edit.php?post_status=future&post_type=post' ) . '">View all scheduled posts</a></li>';
} else {
echo '<li>No upcoming posts or tasks scheduled.</li>';
}
?>
</ul>
</div>
</div>
<?php elseif ( $active_tab == 'video_embedding' ) : ?>
<!-- VIDEO EMBEDDING INTELLIGENCE TAB -->
<?php
// Get module instance
$video_module = isset($GLOBALS['finnish_global_video_embedding']) ?
$GLOBALS['finnish_global_video_embedding'] : null;
if (!$video_module) {
echo '<div class="notice notice-error"><p>Video Embedding Intelligence module not loaded.</p></div>';
// Try to create instance
if (class_exists('Finnish_Global_Video_Embedding_Intelligence')) {
$video_module = new Finnish_Global_Video_Embedding_Intelligence();
$GLOBALS['finnish_global_video_embedding'] = $video_module;
}
}
// Handle form submissions
if (isset($_POST['video_embedding_action'])) {
if ($_POST['video_embedding_action'] == 'save_settings' && wp_verify_nonce($_POST['video_embedding_nonce'], 'video_embedding_settings')) {
// Save settings
if ($video_module) {
$video_module->update_setting('enabled', isset($_POST['enabled']) ? '1' : '0');
$video_module->update_setting('youtube_api_key', sanitize_text_field($_POST['youtube_api_key']));
$video_module->update_setting('vimeo_access_token', sanitize_text_field($_POST['vimeo_access_token']));
$video_module->update_setting('max_videos_per_article', intval($_POST['max_videos_per_article']));
$video_module->update_setting('weekly_limit', intval($_POST['weekly_limit']));
$video_module->update_setting('min_relevance_threshold', intval($_POST['min_relevance_threshold']));
$video_module->update_setting('auto_approve_threshold', intval($_POST['auto_approve_threshold']));
$video_module->update_setting('auto_embed_enabled', isset($_POST['auto_embed_enabled']) ? '1' : '0');
Finnish_Global_Logger::log('Video Embedding: Settings saved successfully');
echo '<div class="notice notice-success"><p>Settings saved successfully!</p></div>';
}
} elseif ($_POST['video_embedding_action'] == 'run_manual_scan' && $video_module) {
$results = $video_module->process_articles();
Finnish_Global_Logger::log('Video Embedding: Manual scan completed');
echo '<div class="notice notice-success"><p>Manual scan completed! Found ' . count($results) . ' video suggestions.</p></div>';
} elseif ($_POST['video_embedding_action'] == 'test_connections' && $video_module) {
$test_results = $video_module->test_api_connections();
echo '<div class="notice notice-info">';
echo '<h4>API Connection Test Results:</h4>';
foreach ($test_results as $platform => $result) {
$status = $result['success'] ? '✅' : '❌';
$color = $result['success'] ? 'green' : 'red';
echo '<p><strong>' . ucfirst($platform) . ':</strong> <span style="color: ' . $color . ';">' . $status . ' ' . $result['message'] . '</span></p>';
}
echo '</div>';
Finnish_Global_Logger::log('Video Embedding: API connection test completed');
}
}
?>
<div class="finnish-network-card">
<h2>🎥 Video Embedding Intelligence</h2>
<p>Automatically find and embed relevant YouTube and Vimeo videos into your articles for enhanced SEO and engagement.</p>
<!-- Module Status -->
<div style="background: #f0f0f0; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; text-align: center;">
<div>
<h4 style="margin: 0; color: #2271b1;">Status</h4>
<p style="margin: 5px 0; font-weight: bold; color: <?php echo $video_module && $video_module->get_setting('enabled') === '1' ? '#00a32a' : '#d63638'; ?>;">
<?php echo $video_module && $video_module->get_setting('enabled') === '1' ? '✅ Active' : '❌ Inactive'; ?>
</p>
</div>
<div>
<h4 style="margin: 0; color: #2271b1;">Videos Embedded</h4>
<p style="margin: 5px 0; font-weight: bold;">
<?php
if ($video_module) {
echo $video_module->get_total_embedded_videos();
} else {
echo '0';
}
?>
</p>
</div>
<div>
<h4 style="margin: 0; color: #2271b1;">Pending Suggestions</h4>
<p style="margin: 5px 0; font-weight: bold;">
<?php
if ($video_module) {
echo $video_module->get_pending_suggestions_count();
} else {
echo '0';
}
?>
</p>
</div>
<div>
<h4 style="margin: 0; color: #2271b1;">This Week</h4>
<p style="margin: 5px 0; font-weight: bold;">
<?php
if ($video_module) {
echo $video_module->get_weekly_embedded_count() . '/' . $video_module->get_setting('weekly_limit', '10');
} else {
echo '0/10';
}
?>
</p>
</div>
</div>
</div>
<!-- Settings Panel -->
<form method="post" style="margin-bottom: 30px;">
<?php wp_nonce_field('video_embedding_settings', 'video_embedding_nonce'); ?>
<input type="hidden" name="video_embedding_action" value="save_settings">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 30px;">
<!-- API Configuration -->
<div>
<h3>🔑 API Configuration</h3>
<table class="form-table">
<tr>
<th scope="row">YouTube API Key</th>
<td>
<input type="text" name="youtube_api_key"
value="<?php echo $video_module ? esc_attr($video_module->get_setting('youtube_api_key', '')) : ''; ?>"
class="regular-text" placeholder="Your YouTube Data API v3 key">
<p class="description">Get your API key from <a href="https://console.developers.google.com/" target="_blank">Google Cloud Console</a></p>
</td>
</tr>
<tr>
<th scope="row">Vimeo Access Token</th>
<td>
<input type="text" name="vimeo_access_token"
value="<?php echo $video_module ? esc_attr($video_module->get_setting('vimeo_access_token', '')) : ''; ?>"
class="regular-text" placeholder="Your Vimeo API access token">
<p class="description">Get your token from <a href="https://developer.vimeo.com/" target="_blank">Vimeo Developers</a></p>
</td>
</tr>
</table>
</div>
<!-- Embedding Settings -->
<div>
<h3>⚙️ Embedding Settings</h3>
<table class="form-table">
<tr>
<th scope="row">Max Videos per Article</th>
<td>
<input type="number" name="max_videos_per_article"
value="<?php echo $video_module ? intval($video_module->get_setting('max_videos_per_article', '1')) : 1; ?>"
min="1" max="5" class="small-text">
<p class="description">Maximum videos to embed in a single article (1-5)</p>
</td>
</tr>
<tr>
<th scope="row">Weekly Embedding Limit</th>
<td>
<input type="number" name="weekly_limit"
value="<?php echo $video_module ? intval($video_module->get_setting('weekly_limit', '10')) : 10; ?>"
min="1" max="100" class="small-text">
<p class="description">Maximum videos to embed per week</p>
</td>
</tr>
<tr>
<th scope="row">Minimum Relevance (%)</th>
<td>
<input type="number" name="min_relevance_threshold"
value="<?php echo $video_module ? intval($video_module->get_setting('min_relevance_threshold', '75')) : 75; ?>"
min="50" max="95" class="small-text">
<p class="description">Minimum relevance score to suggest a video (50-95%)</p>
</td>
</tr>
<tr>
<th scope="row">Auto-Approve Threshold (%)</th>
<td>
<input type="number" name="auto_approve_threshold"
value="<?php echo $video_module ? intval($video_module->get_setting('auto_approve_threshold', '90')) : 90; ?>"
min="80" max="100" class="small-text">
<p class="description">Relevance score for automatic embedding (80-100%)</p>
</td>
</tr>
</table>
</div>
</div>
<div style="margin-top: 20px;">
<label>
<input type="checkbox" name="enabled" value="1"
<?php echo $video_module && $video_module->get_setting('enabled') === '1' ? 'checked' : ''; ?>>
Enable Video Embedding Intelligence
</label>
<br><br>
<label>
<input type="checkbox" name="auto_embed_enabled" value="1"
<?php echo $video_module && $video_module->get_setting('auto_embed_enabled') === '1' ? 'checked' : ''; ?>>
Enable Automatic Embedding (videos above auto-approve threshold will be embedded immediately)
</label>
</div>
<div style="margin-top: 20px;">
<button type="submit" class="button button-primary">Save Settings</button>
<button type="submit" name="video_embedding_action" value="test_connections" class="button button-secondary" style="margin-left: 10px;">Test API Connections</button>
<button type="submit" name="video_embedding_action" value="run_manual_scan" class="button button-secondary" style="margin-left: 10px;">Run Manual Scan</button>
</div>
</form>
<!-- Video Suggestions Table -->
<h3>📋 Video Suggestions</h3>
<div style="background: white; border: 1px solid #ccd0d4; border-radius: 4px;">
<?php
if ($video_module) {
$suggestions = $video_module->get_pending_suggestions();
if (!empty($suggestions)) {
echo '<table class="wp-list-table widefat fixed striped">';
echo '<thead><tr>';
echo '<th>Article</th><th>Video</th><th>Platform</th><th>Relevance</th><th>Status</th><th>Actions</th>';
echo '</tr></thead><tbody>';
foreach ($suggestions as $suggestion) {
echo '<tr>';
echo '<td><a href="' . get_edit_post_link($suggestion['article_id']) . '">' . get_the_title($suggestion['article_id']) . '</a></td>';
echo '<td>';
echo '<strong>' . esc_html($suggestion['video_title']) . '</strong><br>';
echo '<small>' . esc_html(substr($suggestion['video_description'], 0, 100)) . '...</small>';
echo '</td>';
echo '<td>' . ucfirst($suggestion['platform']) . '</td>';
echo '<td><span style="color: ' . ($suggestion['relevance_score'] >= 90 ? '#00a32a' : ($suggestion['relevance_score'] >= 75 ? '#dba617' : '#d63638')) . '; font-weight: bold;">' . $suggestion['relevance_score'] . '%</span></td>';
echo '<td>' . ucfirst($suggestion['status']) . '</td>';
echo '<td>';
if ($suggestion['status'] === 'pending') {
echo '<button class="button button-small" onclick="embedVideo(' . $suggestion['id'] . ')">Embed</button> ';
echo '<button class="button button-small" onclick="rejectVideo(' . $suggestion['id'] . ')">Reject</button>';
}
echo '</td>';
echo '</tr>';
}
echo '</tbody></table>';
} else {
echo '<p style="padding: 20px; text-align: center; color: #666;">No pending video suggestions. Run a manual scan to find videos for your articles.</p>';
}
} else {
echo '<p style="padding: 20px; text-align: center; color: #d63638;">Video Embedding Intelligence module not available.</p>';
}
?>
</div>
<!-- Embedding History -->
<h3 style="margin-top: 30px;">📜 Embedding History</h3>
<div style="background: white; border: 1px solid #ccd0d4; border-radius: 4px;">
<?php
if ($video_module) {
$history = $video_module->get_embedding_history(20);
if (!empty($history)) {
echo '<table class="wp-list-table widefat fixed striped">';
echo '<thead><tr>';
echo '<th>Date</th><th>Article</th><th>Video</th><th>Platform</th><th>Status</th>';
echo '</tr></thead><tbody>';
foreach ($history as $entry) {
echo '<tr>';
echo '<td>' . date('M j, Y g:i A', strtotime($entry['embedded_date'])) . '</td>';
echo '<td><a href="' . get_permalink($entry['article_id']) . '">' . get_the_title($entry['article_id']) . '</a></td>';
echo '<td>' . esc_html($entry['video_title']) . '</td>';
echo '<td>' . ucfirst($entry['platform']) . '</td>';
echo '<td><span style="color: #00a32a;">✅ Embedded</span></td>';
echo '</tr>';
}
echo '</tbody></table>';
} else {
echo '<p style="padding: 20px; text-align: center; color: #666;">No videos embedded yet.</p>';
}
}
?>
</div>
<!-- Process Logs -->
<h3 style="margin-top: 30px;">📋 Process Logs</h3>
<div style="background: #f9f9f9; border: 1px solid #ddd; border-radius: 4px; max-height: 300px; overflow-y: auto;">
<ul style="margin: 0; padding: 10px; font-family: monospace; font-size: 12px; line-height: 1.4;">
<?php
$video_logs = array_filter(Finnish_Global_Logger::get_logs(), function($log) {
return stripos($log, 'Video Embedding') !== false;
});
if (!empty($video_logs)) {
foreach (array_slice($video_logs, 0, 50) as $log) {
echo '<li>' . esc_html($log) . '</li>';
}
} else {
echo '<li>No video embedding logs yet.</li>';
}
?>
</ul>
</div>
<!-- Knowledge Base / FAQ -->
<div style="margin-top: 30px; background: #f0f6fc; padding: 20px; border-radius: 8px; border: 1px solid #c3d7f0;">
<h3 style="color: #135e96; margin-top: 0;">📚 Knowledge Base & FAQ</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 30px;">
<div>
<h4 style="color: #2271b1;">How Video Matching Works</h4>
<ul style="line-height: 1.6;">
<li><strong>Content Analysis:</strong> Extracts keywords from article title and content</li>
<li><strong>API Search:</strong> Searches YouTube and Vimeo for relevant videos</li>
<li><strong>Relevance Scoring:</strong> Calculates match quality based on title/description overlap</li>
<li><strong>Smart Placement:</strong> Finds optimal insertion points in article content</li>
</ul>
<h4 style="color: #2271b1;">SEO Benefits</h4>
<ul style="line-height: 1.6;">
<li>Schema.org VideoObject markup for rich snippets</li>
<li>Responsive embeds with proper alt text</li>
<li>Increased time-on-page and engagement</li>
<li>Video results in search engines</li>
</ul>
</div>
<div>
<h4 style="color: #2271b1;">Best Practices</h4>
<ul style="line-height: 1.6;">
<li><strong>Quality Control:</strong> Review suggestions before auto-embedding</li>
<li><strong>Relevance Threshold:</strong> Keep above 75% for best results</li>
<li><strong>Weekly Limits:</strong> Don't over-embed - quality over quantity</li>
<li><strong>Manual Review:</strong> Check placements in longer articles</li>
</ul>
<h4 style="color: #2271b1;">Troubleshooting</h4>
<ul style="line-height: 1.6;">
<li>Check API keys are valid and have quota remaining</li>
<li>Ensure articles have sufficient content for analysis</li>
<li>Review logs for any API errors or rate limits</li>
<li>Test with manual scan for immediate feedback</li>
</ul>
</div>
</div>
</div>
</div>
<script>
function embedVideo(suggestionId) {
if (confirm('Embed this video?')) {
fetch(ajaxurl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: new URLSearchParams({
action: 'video_embedding_action',
video_action: 'embed_video',
suggestion_id: suggestionId,
nonce: '<?php echo wp_create_nonce('video_embedding_ajax'); ?>'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Error: ' + data.data);
}
});
}
}
function rejectVideo(suggestionId) {
if (confirm('Reject this video suggestion?')) {
fetch(ajaxurl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: new URLSearchParams({
action: 'video_embedding_action',
video_action: 'reject_video',
suggestion_id: suggestionId,
nonce: '<?php echo wp_create_nonce('video_embedding_ajax'); ?>'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Error: ' + data.data);
}
});
}
}
</script>
<?php elseif ( $active_tab == 'video_embedding' ) : ?>
<!-- VIDEO EMBEDDING INTELLIGENCE TAB -->
<?php
// Get module instance
$video_module = isset($GLOBALS['finnish_global_video_embedding']) ?
$GLOBALS['finnish_global_video_embedding'] : null;
if (!$video_module) {
echo '<div class="notice notice-error"><p>Video Embedding Intelligence module not loaded.</p></div>';
// Try to create instance
if (class_exists('Finnish_Global_Video_Embedding_Intelligence')) {
$video_module = new Finnish_Global_Video_Embedding_Intelligence();
$GLOBALS['finnish_global_video_embedding'] = $video_module;
}
}
// Handle form submissions
if (isset($_POST['video_embedding_action'])) {
if ($_POST['video_embedding_action'] == 'save_settings' && wp_verify_nonce($_POST['video_embedding_nonce'], 'video_embedding_settings')) {
// Save settings
if ($video_module) {
$video_module->update_setting('enabled', isset($_POST['enabled']) ? '1' : '0');
$video_module->update_setting('youtube_api_key', sanitize_text_field($_POST['youtube_api_key']));
$video_module->update_setting('vimeo_access_token', sanitize_text_field($_POST['vimeo_access_token']));
$video_module->update_setting('max_videos_per_article', intval($_POST['max_videos_per_article']));
$video_module->update_setting('weekly_limit', intval($_POST['weekly_limit']));
$video_module->update_setting('min_relevance_threshold', intval($_POST['min_relevance_threshold']));
$video_module->update_setting('auto_approve_threshold', intval($_POST['auto_approve_threshold']));
$video_module->update_setting('auto_embed_enabled', isset($_POST['auto_embed_enabled']) ? '1' : '0');
Finnish_Global_Logger::log('Video Embedding: Settings saved successfully');
echo '<div class="notice notice-success"><p>Settings saved successfully!</p></div>';
}
} elseif ($_POST['video_embedding_action'] == 'run_manual_scan' && $video_module) {
$results = $video_module->process_articles();
Finnish_Global_Logger::log('Video Embedding: Manual scan completed');
echo '<div class="notice notice-success"><p>Manual scan completed! Found ' . count($results) . ' video suggestions.</p></div>';
} elseif ($_POST['video_embedding_action'] == 'test_connections' && $video_module) {
$test_results = $video_module->test_api_connections();
echo '<div class="notice notice-info">';
echo '<h4>API Connection Test Results:</h4>';
foreach ($test_results as $platform => $result) {
$status = $result['success'] ? '✅' : '❌';
$color = $result['success'] ? 'green' : 'red';
echo '<p><strong>' . ucfirst($platform) . ':</strong> <span style="color: ' . $color . ';">' . $status . ' ' . $result['message'] . '</span></p>';
}
echo '</div>';
Finnish_Global_Logger::log('Video Embedding: API connection test completed');
}
}
?>
<div class="finnish-network-card">
<h2>🎥 Video Embedding Intelligence</h2>
<p>Automatically find and embed relevant YouTube and Vimeo videos into your articles for enhanced SEO and engagement.</p>
<!-- Module Status -->
<div style="background: #f0f0f0; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; text-align: center;">
<div>
<h4 style="margin: 0; color: #2271b1;">Status</h4>
<p style="margin: 5px 0; font-weight: bold; color: <?php echo $video_module && $video_module->get_setting('enabled') === '1' ? '#00a32a' : '#d63638'; ?>;">
<?php echo $video_module && $video_module->get_setting('enabled') === '1' ? '✅ Active' : '❌ Inactive'; ?>
</p>
</div>
<div>
<h4 style="margin: 0; color: #2271b1;">Videos Embedded</h4>
<p style="margin: 5px 0; font-weight: bold;">
<?php
if ($video_module) {
echo $video_module->get_total_embedded_videos();
} else {
echo '0';
}
?>
</p>
</div>
<div>
<h4 style="margin: 0; color: #2271b1;">Pending Suggestions</h4>
<p style="margin: 5px 0; font-weight: bold;">
<?php
if ($video_module) {
echo $video_module->get_pending_suggestions_count();
} else {
echo '0';
}
?>
</p>
</div>
<div>
<h4 style="margin: 0; color: #2271b1;">This Week</h4>
<p style="margin: 5px 0; font-weight: bold;">
<?php
if ($video_module) {
echo $video_module->get_weekly_embedded_count() . '/' . $video_module->get_setting('weekly_limit', '10');
} else {
echo '0/10';
}
?>
</p>
</div>
</div>
</div>
<!-- Settings Panel -->
<form method="post" style="margin-bottom: 30px;">
<?php wp_nonce_field('video_embedding_settings', 'video_embedding_nonce'); ?>
<input type="hidden" name="video_embedding_action" value="save_settings">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 30px;">
<!-- API Configuration -->
<div>
<h3>🔑 API Configuration</h3>
<table class="form-table">
<tr>
<th scope="row">YouTube API Key</th>
<td>
<input type="text" name="youtube_api_key"
value="<?php echo $video_module ? esc_attr($video_module->get_setting('youtube_api_key', '')) : ''; ?>"
class="regular-text" placeholder="Your YouTube Data API v3 key">
<p class="description">Get your API key from <a href="https://console.developers.google.com/" target="_blank">Google Cloud Console</a></p>
</td>
</tr>
<tr>
<th scope="row">Vimeo Access Token</th>
<td>
<input type="text" name="vimeo_access_token"
value="<?php echo $video_module ? esc_attr($video_module->get_setting('vimeo_access_token', '')) : ''; ?>"
class="regular-text" placeholder="Your Vimeo API access token">
<p class="description">Get your token from <a href="https://developer.vimeo.com/" target="_blank">Vimeo Developers</a></p>
</td>
</tr>
</table>
</div>
<!-- Embedding Settings -->
<div>
<h3>⚙️ Embedding Settings</h3>
<table class="form-table">
<tr>
<th scope="row">Max Videos per Article</th>
<td>
<input type="number" name="max_videos_per_article"
value="<?php echo $video_module ? intval($video_module->get_setting('max_videos_per_article', '1')) : 1; ?>"
min="1" max="5" class="small-text">
<p class="description">Maximum videos to embed in a single article (1-5)</p>
</td>
</tr>
<tr>
<th scope="row">Weekly Embedding Limit</th>
<td>
<input type="number" name="weekly_limit"
value="<?php echo $video_module ? intval($video_module->get_setting('weekly_limit', '10')) : 10; ?>"
min="1" max="100" class="small-text">
<p class="description">Maximum videos to embed per week</p>
</td>
</tr>
<tr>
<th scope="row">Minimum Relevance (%)</th>
<td>
<input type="number" name="min_relevance_threshold"
value="<?php echo $video_module ? intval($video_module->get_setting('min_relevance_threshold', '75')) : 75; ?>"
min="50" max="95" class="small-text">
<p class="description">Minimum relevance score to suggest a video (50-95%)</p>
</td>
</tr>
<tr>
<th scope="row">Auto-Approve Threshold (%)</th>
<td>
<input type="number" name="auto_approve_threshold"
value="<?php echo $video_module ? intval($video_module->get_setting('auto_approve_threshold', '90')) : 90; ?>"
min="80" max="100" class="small-text">
<p class="description">Relevance score for automatic embedding (80-100%)</p>
</td>
</tr>
</table>
</div>
</div>
<div style="margin-top: 20px;">
<label>
<input type="checkbox" name="enabled" value="1"
<?php echo $video_module && $video_module->get_setting('enabled') === '1' ? 'checked' : ''; ?>>
Enable Video Embedding Intelligence
</label>
<br><br>
<label>
<input type="checkbox" name="auto_embed_enabled" value="1"
<?php echo $video_module && $video_module->get_setting('auto_embed_enabled') === '1' ? 'checked' : ''; ?>>
Enable Automatic Embedding (videos above auto-approve threshold will be embedded immediately)
</label>
</div>
<div style="margin-top: 20px;">
<button type="submit" class="button button-primary">Save Settings</button>
<button type="submit" name="video_embedding_action" value="test_connections" class="button button-secondary" style="margin-left: 10px;">Test API Connections</button>
<button type="submit" name="video_embedding_action" value="run_manual_scan" class="button button-secondary" style="margin-left: 10px;">Run Manual Scan</button>
</div>
</form>
<!-- Video Suggestions Table -->
<h3>📋 Video Suggestions</h3>
<div style="background: white; border: 1px solid #ccd0d4; border-radius: 4px;">
<?php
if ($video_module) {
$suggestions = $video_module->get_pending_suggestions();
if (!empty($suggestions)) {
echo '<table class="wp-list-table widefat fixed striped">';
echo '<thead><tr>';
echo '<th>Article</th><th>Video</th><th>Platform</th><th>Relevance</th><th>Status</th><th>Actions</th>';
echo '</tr></thead><tbody>';
foreach ($suggestions as $suggestion) {
echo '<tr>';
echo '<td><a href="' . get_edit_post_link($suggestion['article_id']) . '">' . get_the_title($suggestion['article_id']) . '</a></td>';
echo '<td>';
echo '<strong>' . esc_html($suggestion['video_title']) . '</strong><br>';
echo '<small>' . esc_html(substr($suggestion['video_description'], 0, 100)) . '...</small>';
echo '</td>';
echo '<td>' . ucfirst($suggestion['platform']) . '</td>';
echo '<td><span style="color: ' . ($suggestion['relevance_score'] >= 90 ? '#00a32a' : ($suggestion['relevance_score'] >= 75 ? '#dba617' : '#d63638')) . '; font-weight: bold;">' . $suggestion['relevance_score'] . '%</span></td>';
echo '<td>' . ucfirst($suggestion['status']) . '</td>';
echo '<td>';
if ($suggestion['status'] === 'pending') {
echo '<button class="button button-small" onclick="embedVideo(' . $suggestion['id'] . ')">Embed</button> ';
echo '<button class="button button-small" onclick="rejectVideo(' . $suggestion['id'] . ')">Reject</button>';
}
echo '</td>';
echo '</tr>';
}
echo '</tbody></table>';
} else {
echo '<p style="padding: 20px; text-align: center; color: #666;">No pending video suggestions. Run a manual scan to find videos for your articles.</p>';
}
} else {
echo '<p style="padding: 20px; text-align: center; color: #d63638;">Video Embedding Intelligence module not available.</p>';
}
?>
</div>
<!-- Embedding History -->
<h3 style="margin-top: 30px;">📜 Embedding History</h3>
<div style="background: white; border: 1px solid #ccd0d4; border-radius: 4px;">
<?php
if ($video_module) {
$history = $video_module->get_embedding_history(20);
if (!empty($history)) {
echo '<table class="wp-list-table widefat fixed striped">';
echo '<thead><tr>';
echo '<th>Date</th><th>Article</th><th>Video</th><th>Platform</th><th>Status</th>';
echo '</tr></thead><tbody>';
foreach ($history as $entry) {
echo '<tr>';
echo '<td>' . date('M j, Y g:i A', strtotime($entry['embedded_date'])) . '</td>';
echo '<td><a href="' . get_permalink($entry['article_id']) . '">' . get_the_title($entry['article_id']) . '</a></td>';
echo '<td>' . esc_html($entry['video_title']) . '</td>';
echo '<td>' . ucfirst($entry['platform']) . '</td>';
echo '<td><span style="color: #00a32a;">✅ Embedded</span></td>';
echo '</tr>';
}
echo '</tbody></table>';
} else {
echo '<p style="padding: 20px; text-align: center; color: #666;">No videos embedded yet.</p>';
}
}
?>
</div>
<!-- Process Logs -->
<h3 style="margin-top: 30px;">📋 Process Logs</h3>
<div style="background: #f9f9f9; border: 1px solid #ddd; border-radius: 4px; max-height: 300px; overflow-y: auto;">
<ul style="margin: 0; padding: 10px; font-family: monospace; font-size: 12px; line-height: 1.4;">
<?php
$video_logs = array_filter(Finnish_Global_Logger::get_logs(), function($log) {
return stripos($log, 'Video Embedding') !== false;
});
if (!empty($video_logs)) {
foreach (array_slice($video_logs, 0, 50) as $log) {
echo '<li>' . esc_html($log) . '</li>';
}
} else {
echo '<li>No video embedding logs yet.</li>';
}
?>
</ul>
</div>
<!-- Knowledge Base / FAQ -->
<div style="margin-top: 30px; background: #f0f6fc; padding: 20px; border-radius: 8px; border: 1px solid #c3d7f0;">
<h3 style="color: #135e96; margin-top: 0;">📚 Knowledge Base & FAQ</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 30px;">
<div>
<h4 style="color: #2271b1;">How Video Matching Works</h4>
<ul style="line-height: 1.6;">
<li><strong>Content Analysis:</strong> Extracts keywords from article title and content</li>
<li><strong>API Search:</strong> Searches YouTube and Vimeo for relevant videos</li>
<li><strong>Relevance Scoring:</strong> Calculates match quality based on title/description overlap</li>
<li><strong>Smart Placement:</strong> Finds optimal insertion points in article content</li>
</ul>
<h4 style="color: #2271b1;">SEO Benefits</h4>
<ul style="line-height: 1.6;">
<li>Schema.org VideoObject markup for rich snippets</li>
<li>Responsive embeds with proper alt text</li>
<li>Increased time-on-page and engagement</li>
<li>Video results in search engines</li>
</ul>
</div>
<div>
<h4 style="color: #2271b1;">Best Practices</h4>
<ul style="line-height: 1.6;">
<li><strong>Quality Control:</strong> Review suggestions before auto-embedding</li>
<li><strong>Relevance Threshold:</strong> Keep above 75% for best results</li>
<li><strong>Weekly Limits:</strong> Don't over-embed - quality over quantity</li>
<li><strong>Manual Review:</strong> Check placements in longer articles</li>
</ul>
<h4 style="color: #2271b1;">Troubleshooting</h4>
<ul style="line-height: 1.6;">
<li>Check API keys are valid and have quota remaining</li>
<li>Ensure articles have sufficient content for analysis</li>
<li>Review logs for any API errors or rate limits</li>
<li>Test with manual scan for immediate feedback</li>
</ul>
</div>
</div>
</div>
</div>
<script>
function embedVideo(suggestionId) {
if (confirm('Embed this video?')) {
fetch(ajaxurl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: new URLSearchParams({
action: 'video_embedding_action',
video_action: 'embed_video',
suggestion_id: suggestionId,
nonce: '<?php echo wp_create_nonce('video_embedding_ajax'); ?>'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Error: ' + data.data);
}
});
}
}
function rejectVideo(suggestionId) {
if (confirm('Reject this video suggestion?')) {
fetch(ajaxurl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: new URLSearchParams({
action: 'video_embedding_action',
video_action: 'reject_video',
suggestion_id: suggestionId,
nonce: '<?php echo wp_create_nonce('video_embedding_ajax'); ?>'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Error: ' + data.data);
}
});
}
}
</script>
<?php elseif ( $active_tab == 'settings' ) : ?>
<!-- SETTINGS TAB -->
<div class="finnish-network-card">
<h2>API Configuration</h2>
<?php
// Check if ScraperAPI is configured
$scraper_api_configured = get_option( 'finnish_global_scraper_api_key', '' );
if ( empty( $scraper_api_configured ) ) {
echo '<div class="notice notice-info" style="margin: 15px 0;"><p><strong>💡 Tip:</strong> Add a ScraperAPI key below to enable URL Rewriter and Category Scraper modules to scrape any website, even those with CloudFlare protection. <a href="https://scraperapi.com" target="_blank">Get free API key (5,000 requests/month)</a></p></div>';
}
// Handle CronJob actions
$cronjob_api_key = get_option( 'finnish_global_cronjob_api_key', '' );
$cronjob_job_id = get_option( 'finnish_global_cronjob_id', '' );
if ( isset( $_GET['cronjob_action'] ) && isset( $_GET['_wpnonce'] ) ) {
if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'finnish_global_cronjob_action' ) ) {
echo '<div class="notice notice-error"><p>Security check failed.</p></div>';
} else {
$service = new Finnish_Global_CronJob_Service();
$action = sanitize_text_field( $_GET['cronjob_action'] );
if ( $action === 'test' ) {
$result = $service->test_connection();
if ( $result['success'] ) {
echo '<div class="notice notice-success"><p>' . esc_html( $result['message'] ) . '</p></div>';
} else {
echo '<div class="notice notice-error"><p>' . esc_html( $result['message'] ) . '</p></div>';
}
} elseif ( $action === 'setup' ) {
$result = $service->setup_cron_job();
if ( $result['success'] ) {
echo '<div class="notice notice-success"><p>' . esc_html( $result['message'] ) . '</p></div>';
$cronjob_job_id = get_option( 'finnish_global_cronjob_id', '' ); // Refresh
} else {
echo '<div class="notice notice-error"><p>' . esc_html( $result['message'] ) . '</p></div>';
}
} elseif ( $action === 'delete' ) {
$result = $service->delete_cron_job();
if ( $result['success'] ) {
echo '<div class="notice notice-success"><p>' . esc_html( $result['message'] ) . '</p></div>';
$cronjob_job_id = ''; // Clear
} else {
echo '<div class="notice notice-error"><p>' . esc_html( $result['message'] ) . '</p></div>';
}
}
}
}
?>
<form method="post" action="options.php">
<?php
settings_fields( 'finnish_global_automation_options' );
do_settings_sections( 'finnish_global_automation_options' );
?>
<h3 style="margin-top: 30px;">Cron-Job.org Integration (24/7 Automatic Scheduling)</h3>
<p>Enter your cron-job.org API key to enable automatic 24/7 scheduler execution without requiring a browser or visitors.</p>
</table>
<h3 style="margin-top: 30px;">AI Prompts (Advanced)</h3>
<p>Customize the AI prompts used for content generation. Leave blank to use defaults.</p>
<table class="form-table">
<tr>
<th scope="row"><label for="finnish_global_prompt_content">Content Rewrite Prompt</label></th>
<td>
<textarea id="finnish_global_prompt_content" name="finnish_global_prompt_content" rows="6" class="large-text code"><?php echo esc_textarea( get_option( 'finnish_global_prompt_content', '' ) ); ?></textarea>
<p class="description">Prompt for rewriting article content. Variables: {target_lang}, {primary_keyword}, {internal_links_list}, {original_content}</p>
</td>
</tr>
<tr>
<th scope="row"><label for="finnish_global_prompt_title">Title Generation Prompt</label></th>
<td>
<textarea id="finnish_global_prompt_title" name="finnish_global_prompt_title" rows="4" class="large-text code"><?php echo esc_textarea( get_option( 'finnish_global_prompt_title', '' ) ); ?></textarea>
<p class="description">Prompt for generating SEO-optimized titles. Variables: {target_lang}, {primary_keyword}, {rewritten_content}</p>
</td>
</tr>
<tr>
<th scope="row"><label for="finnish_global_prompt_meta">Meta Description Prompt</label></th>
<td>
<textarea id="finnish_global_prompt_meta" name="finnish_global_prompt_meta" rows="4" class="large-text code"><?php echo esc_textarea( get_option( 'finnish_global_prompt_meta', '' ) ); ?></textarea>
<p class="description">Prompt for generating meta descriptions. Variables: {target_lang}, {primary_keyword}, {rewritten_content}</p>
</td>
</tr>
</table>
<h3 style="margin-top: 30px;">Advanced Content Features (v2.1.55)</h3>
<p>Enable powerful SEO and content quality features.</p>
<table class="form-table">
<tr>
<th scope="row">Auto-Schema Markup</th>
<td>
<label>
<input type="checkbox" name="finnish_global_enable_schema" value="1" <?php checked( get_option( 'finnish_global_enable_schema', true ) ); ?>>
Enable automatic Schema.org JSON-LD markup for rich snippets in Google
</label>
<p class="description">Adds invisible structured data to posts for star ratings, FAQ boxes, and rich search results.</p>
</td>
</tr>
<tr>
<th scope="row">Smart Keyword Density</th>
<td>
<label>
<input type="checkbox" name="finnish_global_enable_keyword_density" value="1" <?php checked( get_option( 'finnish_global_enable_keyword_density', true ) ); ?>>
Enable keyword density control (prevents Google penalties)
</label>
<p class="description">Ensures keywords appear naturally (not too much, not too little).</p>
<p style="margin-top: 10px;">
<label>Min Density: <input type="number" step="0.1" name="finnish_global_min_density" value="<?php echo esc_attr( get_option( 'finnish_global_min_density', 0.5 ) ); ?>" style="width: 60px;">%</label>
<label>Max Density: <input type="number" step="0.1" name="finnish_global_max_density" value="<?php echo esc_attr( get_option( 'finnish_global_max_density', 2.5 ) ); ?>" style="width: 60px;">%</label>
</p>
</td>
</tr>
<tr>
<th scope="row">Style Mixer</th>
<td>
<label>
<input type="checkbox" name="finnish_global_enable_style_mixer" value="1" <?php checked( get_option( 'finnish_global_enable_style_mixer', false ) ); ?>>
Enable random writing style variation
</label>
<p class="description">Randomizes blog voice so posts don't all sound the same (one per line):</p>
<textarea name="finnish_global_style_presets" rows="5" class="large-text code" placeholder="Write as breaking news with urgency Write as an opinion piece Write as a numbered listicle Write as a tutorial guide Write as deep analysis"><?php echo esc_textarea( get_option( 'finnish_global_style_presets', '' ) ); ?></textarea>
</td>
</tr>
<tr>
<th scope="row">Internal Linking (v2.3.0)</th>
<td>
<label>
<input type="checkbox" name="finnish_global_enable_internal_links" value="1" <?php checked( get_option( 'finnish_global_enable_internal_links', true ) ); ?>>
Automatically add 2-3 internal links to related posts
</label>
<p class="description">Finds relevant posts based on primary keyword and adds contextual links for better SEO.</p>
</td>
</tr>
</tr>
<tr>
<th scope="row">Content Uniqueness Checker (v2.4.0)</th>
<td>
<label>
<input type="checkbox" name="finnish_global_check_uniqueness" value="1" <?php checked( get_option( 'finnish_global_check_uniqueness', false ) ); ?>>
Verify rewritten content is unique (triggers auto-rewrite if >30% similar to original)
</label>
<p class="description">Prevents duplicate content penalties by ensuring AI rewrites are sufficiently different.</p>
</td>
</tr>
<tr>
<th scope="row">Automated Fact-Checking (v2.4.0)</th>
<td>
<label>
<input type="checkbox" name="finnish_global_enable_fact_check" value="1" <?php checked( get_option( 'finnish_global_enable_fact_check', false ) ); ?>>
Verify and auto-correct factual discrepancies in AI-generated content
</label>
<p class="description">Extracts key facts from original, verifies them in rewrite, and automatically corrects any changes. <strong>Cost:</strong> ~3 extra AI calls per post (~$0.003).</p>
</td>
</tr>
</table>
<h3 style="margin-top: 30px;">Cron-Job.org Integration (24/7 Automatic Scheduling)</h3>
<p>Enter your cron-job.org API key to enable automatic 24/7 scheduler execution without requiring a browser or visitors.</p>
<table class="form-table">
<tr>
<th scope="row"><label for="finnish_global_cronjob_api_key">Cron-Job.org API Key</label></th>
<td>
<?php
if ( !empty( $cronjob_api_key ) ) {
$api_length = strlen( $cronjob_api_key );
if ( $api_length > 4 ) {
$masked_api = str_repeat( '•', $api_length - 4 ) . substr( $cronjob_api_key, -4 );
} else {
$masked_api = str_repeat( '•', $api_length );
}
echo '<input type="password" id="finnish_global_cronjob_api_key" name="finnish_global_cronjob_api_key" value="' . esc_attr( $cronjob_api_key ) . '" class="regular-text" style="font-family: monospace;">';
echo '<span class="description" style="margin-left: 10px;">Current: ' . esc_html( $masked_api ) . '</span>';
} else {
echo '<input type="password" id="finnish_global_cronjob_api_key" name="finnish_global_cronjob_api_key" value="" class="regular-text">';
}
?>
<p class="description">Get your API key from <a href="https://console.cron-job.org/account" target="_blank">cron-job.org console</a> (Account → API).</p>
</td>
</tr>
</table>
<?php submit_button( 'Save All Settings' ); ?>
</form>
<?php if ( ! empty( $cronjob_api_key ) ) : ?>
<hr style="margin: 20px 0;">
<h3>Cron Job Actions</h3>
<p>
<a href="<?php echo wp_nonce_url( admin_url( 'admin.php?page=finnish-network-global&tab=settings&cronjob_action=test' ), 'finnish_global_cronjob_action' ); ?>" class="button">Test Connection</a>
<?php if ( empty( $cronjob_job_id ) ) : ?>
<a href="<?php echo wp_nonce_url( admin_url( 'admin.php?page=finnish-network-global&tab=settings&cronjob_action=setup' ), 'finnish_global_cronjob_action' ); ?>" class="button button-primary">Auto-Setup Cron Job</a>
<?php else : ?>
<span style="color: green; font-weight: bold;">✓ Cron Job Active (ID: <?php echo esc_html( $cronjob_job_id ); ?>)</span>
<a href="<?php echo wp_nonce_url( admin_url( 'admin.php?page=finnish-network-global&tab=settings&cronjob_action=delete' ), 'finnish_global_cronjob_action' ); ?>" class="button button-secondary" onclick="return confirm('Are you sure you want to delete the cron job?')">Delete Cron Job</a>
<?php endif; ?>
</p>
<p class="description">The auto-setup will create a cron job that pings your site every hour to trigger WP Cron, ensuring your schedulers run 24/7.</p>
<?php endif; ?>
</div>
<?php elseif ( $active_tab == 'logs' ) : ?>
<!-- LOGS TAB -->
<div class="finnish-network-card">
<h2>System Logs</h2>
<p>View detailed logs of content generation, scheduling, and errors.</p>
<div style="background: #f0f0f1; padding: 15px; border: 1px solid #ccc; height: 500px; overflow-y: scroll; font-family: monospace; white-space: pre-wrap;">
<?php
$logs = Finnish_Global_Logger::get_logs();
if ( empty( $logs ) ) {
echo 'No logs yet.';
} else {
foreach ( $logs as $log ) {
echo esc_html( $log ) . "\n";
}
}
?>
</div>
<div style="margin-top: 15px;">
<form method="post" action="">
<input type="hidden" name="action" value="finnish_global_clear_logs">
<?php submit_button( 'Clear Logs', 'secondary', 'submit', false ); ?>
</form>
</div>
</div>
<?php elseif ( $active_tab == 'ai_community' ) : ?>
<!-- AI COMMUNITY MANAGER TAB -->
<?php
// FIX: Helper function to read AI Community settings from database
function get_ai_community_setting($key, $default = '') {
global $wpdb;
$settings_table = $wpdb->prefix . 'ai_community_settings';
$value = $wpdb->get_var($wpdb->prepare("SELECT setting_value FROM $settings_table WHERE setting_key = %s", $key));
return $value !== null ? $value : $default;
}
?>
<div class="finnish-network-card">
<h2>🤖 AI Community Manager</h2>
<p>Revolutionary AI-powered commenting system that creates organic community engagement through intelligent personas.</p>
<!-- Master Controls -->
<div class="finnish-network-card" style="margin-bottom: 20px;">
<h3>🎛️ Master Control Center</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px;">
<div>
<h4>System Status</h4>
<?php
// FIX: Read from AI Community settings table, not WordPress options
global $wpdb;
$settings_table = $wpdb->prefix . 'ai_community_settings';
$ai_enabled = get_ai_community_setting('ai_community_enabled', '0') === '1';
$emergency_stop = get_ai_community_setting('ai_community_emergency_stop', '0') === '1';
?>
<label style="display: flex; align-items: center; gap: 10px;">
<input type="checkbox" id="ai_community_enabled" <?php checked($ai_enabled); ?> <?php disabled($emergency_stop); ?>>
<span style="font-weight: bold;">Enable AI Community System</span>
</label>
<?php if ($emergency_stop) : ?>
<p style="color: #d63638; font-weight: bold;">🚨 EMERGENCY STOP ACTIVE</p>
<button class="button button-primary" id="resume_ai_community">Resume System</button>
<?php else : ?>
<button class="button button-secondary" id="emergency_stop_ai" style="margin-left: 10px;">⏸️ Emergency Stop</button>
<?php endif; ?>
</div>
<div>
<h4>Activity Settings</h4>
<label>Daily Comment Limit:
<input type="range" id="daily_comment_limit" min="0" max="50" value="<?php echo get_ai_community_setting('ai_community_daily_limit', '12'); ?>" style="width: 100%;">
<span id="daily_limit_value"><?php echo get_ai_community_setting('ai_community_daily_limit', '12'); ?></span> comments/day
</label>
<label style="margin-top: 10px; display: block;">Auto-Approval Threshold:
<input type="range" id="auto_approval_threshold" min="60" max="95" value="<?php echo get_ai_community_setting('ai_community_auto_approval', '85'); ?>" style="width: 100%;">
<span id="approval_value"><?php echo get_ai_community_setting('ai_community_auto_approval', '85'); ?></span>% quality score
</label>
<label style="margin-top: 10px; display: block;">Max AI Personas:
<input type="range" id="max_personas" min="10" max="200" value="<?php echo get_ai_community_setting('max_personas', '50'); ?>" style="width: 100%;">
<span id="personas_value"><?php echo get_ai_community_setting('max_personas', '50'); ?></span> unique personas
</label>
</div>
</div>
</div>
<!-- Live Analytics Dashboard -->
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 30px;">
<?php
global $wpdb;
// Get AI Community stats
$ai_comments_today = $wpdb->get_var("
SELECT COUNT(*) FROM {$wpdb->prefix}ai_comments_history
WHERE approval_status IN ('approved', 'auto_approved')
AND DATE(comment_date) = CURDATE()
") ?: 0;
$ai_comments_week = $wpdb->get_var("
SELECT COUNT(*) FROM {$wpdb->prefix}ai_comments_history
WHERE approval_status IN ('approved', 'auto_approved')
AND comment_date >= DATE_SUB(NOW(), INTERVAL 7 DAY)
") ?: 0;
$pending_comments = $wpdb->get_var("
SELECT COUNT(*) FROM {$wpdb->prefix}ai_comments_history
WHERE approval_status = 'pending'
") ?: 0;
$active_personas = $wpdb->get_var("
SELECT COUNT(*) FROM {$wpdb->prefix}ai_personas
WHERE status = 'active'
") ?: 0;
$real_responses = $wpdb->get_var("
SELECT SUM(user_responses_count) FROM {$wpdb->prefix}ai_comments_history
WHERE approval_status IN ('approved', 'auto_approved')
AND comment_date >= DATE_SUB(NOW(), INTERVAL 7 DAY)
") ?: 0;
?>
<div class="finnish-network-card">
<h4>📊 Today's Activity</h4>
<div class="finnish-network-stat"><?php echo $ai_comments_today; ?></div>
<div class="finnish-network-stat-label">AI Comments Posted</div>
<p style="margin: 10px 0 0; color: #666; font-size: 0.9em;">
Limit: <?php echo get_ai_community_setting('ai_community_daily_limit', '12'); ?>/day
</p>
</div>
<div class="finnish-network-card">
<h4>📈 Week Performance</h4>
<div class="finnish-network-stat"><?php echo $ai_comments_week; ?></div>
<div class="finnish-network-stat-label">Comments This Week</div>
<p style="margin: 10px 0 0; color: #666; font-size: 0.9em;">
<?php echo $real_responses; ?> user responses generated
</p>
</div>
<div class="finnish-network-card">
<h4>⏳ Pending Review</h4>
<div class="finnish-network-stat" style="color: #dba617;"><?php echo $pending_comments; ?></div>
<div class="finnish-network-stat-label">Comments Awaiting Approval</div>
<?php if ($pending_comments > 0) : ?>
<a href="#pending-queue" class="button button-primary" style="margin-top: 10px;">Review Now</a>
<?php endif; ?>
</div>
<div class="finnish-network-card">
<h4>👥 Active Personas</h4>
<div class="finnish-network-stat" style="color: #00a32a;"><?php echo $active_personas; ?></div>
<div class="finnish-network-stat-label">AI Personas Available</div>
<button class="button button-secondary" style="margin-top: 10px;" onclick="generateNewPersona()">Generate New</button>
</div>
</div>
<!-- Pending Comments Queue -->
<?php if ($pending_comments > 0) : ?>
<div class="finnish-network-card" id="pending-queue">
<h3>⏳ Pending Comments Queue</h3>
<p>Review AI-generated comments before they go live. Comments with quality scores below <?php echo get_ai_community_setting('ai_community_auto_approval', '85'); ?>% require manual approval.</p>
<?php
$pending_comments_data = $wpdb->get_results("
SELECT h.*, p.display_name, p.expertise_areas, posts.post_title
FROM {$wpdb->prefix}ai_comments_history h
LEFT JOIN {$wpdb->prefix}ai_personas p ON h.persona_id = p.id
LEFT JOIN {$wpdb->posts} posts ON h.post_id = posts.ID
WHERE h.approval_status = 'pending'
ORDER BY h.comment_date DESC
LIMIT 10
");
if ($pending_comments_data) :
?>
<div style="margin-bottom: 20px;">
<button class="button button-primary" onclick="bulkApproveSelected()">✅ Bulk Approve Selected</button>
<button class="button button-secondary" onclick="bulkRejectSelected()">❌ Bulk Reject Selected</button>
</div>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th style="width: 30px;"><input type="checkbox" id="select-all-comments"></th>
<th>Comment Preview</th>
<th>Post</th>
<th>Persona</th>
<th style="width: 80px;">Quality</th>
<th style="width: 120px;">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($pending_comments_data as $comment) : ?>
<tr>
<td><input type="checkbox" class="comment-checkbox" data-id="<?php echo $comment->id; ?>"></td>
<td>
<div style="max-width: 300px; overflow: hidden;">
<strong style="color: #0073aa;"><?php echo esc_html($comment->display_name); ?>:</strong>
<p style="margin: 5px 0; line-height: 1.4;"><?php echo wp_trim_words(esc_html($comment->comment_content), 20); ?></p>
</div>
</td>
<td>
<a href="<?php echo get_permalink($comment->post_id); ?>" target="_blank">
<?php echo wp_trim_words(esc_html($comment->post_title), 6); ?>
</a>
</td>
<td>
<div>
<strong><?php echo esc_html($comment->display_name); ?></strong><br>
<small style="color: #666;">
<?php
$expertise = json_decode($comment->expertise_areas, true);
echo esc_html(is_array($expertise) ? implode(', ', array_slice($expertise, 0, 2)) : 'General');
?>
</small>
</div>
</td>
<td>
<div style="text-align: center;">
<span style="font-weight: bold; color: <?php
if ($comment->quality_score >= 85) echo '#00a32a';
elseif ($comment->quality_score >= 70) echo '#dba617';
else echo '#d63638';
?>;"><?php echo $comment->quality_score; ?>%</span>
</div>
</td>
<td>
<button class="button button-primary button-small" onclick="approveComment(<?php echo $comment->id; ?>)">✅ Approve</button>
<button class="button button-secondary button-small" onclick="rejectComment(<?php echo $comment->id; ?>)" style="margin-top: 2px;">❌ Reject</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<?php endif; ?>
<!-- Recent Activity -->
<div class="finnish-network-card">
<h3>📋 Recent AI Community Activity</h3>
<?php
$recent_activity = $wpdb->get_results("
SELECT h.*, p.display_name, posts.post_title, h.user_responses_count
FROM {$wpdb->prefix}ai_comments_history h
LEFT JOIN {$wpdb->prefix}ai_personas p ON h.persona_id = p.id
LEFT JOIN {$wpdb->posts} posts ON h.post_id = posts.ID
WHERE h.approval_status IN ('approved', 'auto_approved')
ORDER BY h.comment_date DESC
LIMIT 15
");
if ($recent_activity) :
?>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>Comment</th>
<th>Post</th>
<th>Persona</th>
<th style="width: 100px;">Engagement</th>
<th style="width: 120px;">Date</th>
</tr>
</thead>
<tbody>
<?php foreach ($recent_activity as $activity) : ?>
<tr>
<td>
<div style="max-width: 300px;">
<?php echo wp_trim_words(esc_html($activity->comment_content), 15); ?>
</div>
</td>
<td>
<a href="<?php echo get_permalink($activity->post_id); ?>#comment-<?php echo $activity->comment_id; ?>" target="_blank">
<?php echo wp_trim_words(esc_html($activity->post_title), 5); ?>
</a>
</td>
<td><?php echo esc_html($activity->display_name); ?></td>
<td>
<span style="color: #00a32a; font-weight: bold;"><?php echo $activity->user_responses_count; ?></span> replies
</td>
<td><?php echo date('M j, H:i', strtotime($activity->comment_date)); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else : ?>
<p style="text-align: center; padding: 40px; color: #666;">
No AI comments have been posted yet. Enable the system above to start generating organic engagement!
</p>
<?php endif; ?>
</div>
<!-- Advanced Settings -->
<div class="finnish-network-card" style="margin-top: 30px;">
<h3>⚙️ Advanced Configuration</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 30px;">
<div>
<h4>🧠 AI Intelligence Settings</h4>
<table class="form-table">
<tr>
<th>Comment Quality Level</th>
<td>
<select id="comment_quality_level">
<option value="good" selected>Good - Thoughtful, engaging comments</option>
<option value="excellent">Excellent - Deep insights, expert level</option>
<option value="basic">Basic - Simple, short comments</option>
</select>
</td>
</tr>
<tr>
<th>Persona Diversity</th>
<td>
<select id="persona_diversity">
<option value="medium" selected>Medium - 50-100 unique personas</option>
<option value="high">High - Unlimited persona generation</option>
<option value="low">Low - 10-20 core personas</option>
</select>
</td>
</tr>
<tr>
<th>Engagement Style</th>
<td>
<select id="engagement_style">
<option value="natural" selected>Natural - Realistic interaction patterns</option>
<option value="active">Active - High engagement frequency</option>
<option value="conservative">Conservative - Minimal, high-quality interactions</option>
</select>
</td>
</tr>
<tr>
<th>Comment Language</th>
<td>
<?php $current_lang = get_ai_community_setting('comment_language', 'en_US'); ?>
<select id="comment_language">
<option value="en_US" <?php selected($current_lang, 'en_US'); ?>>🇺🇸 English (USA)</option>
<option value="en_GB" <?php selected($current_lang, 'en_GB'); ?>>🇬🇧 English (UK)</option>
<option value="es_ES" <?php selected($current_lang, 'es_ES'); ?>>🇪🇸 Español (Spain)</option>
<option value="de_DE" <?php selected($current_lang, 'de_DE'); ?>>🇩🇪 Deutsch (Germany)</option>
<option value="fr_FR" <?php selected($current_lang, 'fr_FR'); ?>>🇫🇷 Français (France)</option>
<option value="it_IT" <?php selected($current_lang, 'it_IT'); ?>>🇮🇹 Italiano (Italy)</option>
<option value="sv_SE" <?php selected($current_lang, 'sv_SE'); ?>>🇸🇪 Svenska (Sweden)</option>
<option value="fi_FI" <?php selected($current_lang, 'fi_FI'); ?>>🇫🇮 Suomi (Finland)</option>
<option value="no_NO" <?php selected($current_lang, 'no_NO'); ?>>🇳🇴 Norsk (Norway)</option>
</select>
<p style="margin: 5px 0; color: #666; font-size: 0.9em;">
AI will generate comments in this language with culturally appropriate names and expressions
</p>
</td>
</tr>
<tr>
<th>Comment Quality Level</th>
<td>
<?php $current_quality = get_ai_community_setting('comment_quality_level', 'natural'); ?>
<select id="comment_quality_level">
<option value="ultra_natural" <?php selected($current_quality, 'ultra_natural'); ?>>🗨️ Ultra-Natural (typos, very casual)</option>
<option value="natural" <?php selected($current_quality, 'natural'); ?>>💬 Natural (conversational with imperfections)</option>
<option value="balanced" <?php selected($current_quality, 'balanced'); ?>>⚖️ Balanced (clean but human-like)</option>
<option value="informed" <?php selected($current_quality, 'informed'); ?>>🧠 Informed (expertise showing)</option>
<option value="professional" <?php selected($current_quality, 'professional'); ?>>👔 Professional (polished but not corporate)</option>
</select>
<p style="margin: 5px 0 0; color: #666; font-size: 0.9em;">
Controls writing style and formality level
</p>
</td>
</tr>
<tr>
<th>Randomize Quality</th>
<td>
<?php $randomize_quality = get_ai_community_setting('randomize_quality', '0'); ?>
<label>
<input type="checkbox" id="randomize_quality" <?php checked($randomize_quality, '1'); ?>>
Vary quality level ±1 for natural diversity
</label>
<p style="margin: 5px 0 0; color: #666; font-size: 0.9em;">
Each comment will vary within 1 level of base setting
</p>
</td>
</tr>
</table>
</div>
<div>
<h4>🛡️ Safety & Quality Controls</h4>
<table class="form-table">
<tr>
<th>Manual Approval Mode</th>
<td>
<label>
<input type="checkbox" id="require_manual_approval">
Require manual approval for ALL AI comments
</label>
</td>
</tr>
<tr>
<th>Detection Sensitivity</th>
<td>
<select id="detection_sensitivity">
<option value="medium" selected>Medium - Balanced safety</option>
<option value="high">High - Maximum safety, may reduce activity</option>
<option value="low">Low - Higher activity, less conservative</option>
</select>
</td>
</tr>
<tr>
<th>Learning System</th>
<td>
<label>
<input type="checkbox" id="learning_enabled" checked>
Enable adaptive learning from engagement results
</label>
</td>
</tr>
</table>
</div>
</div>
<div style="margin-top: 20px; text-align: center;">
<button class="button button-primary button-large" onclick="saveAISettings()">💾 Save All Settings</button>
<button class="button button-secondary" onclick="resetToDefaults()">🔄 Reset to Defaults</button>
</div>
</div>
</div>
<!-- Debug Log Window -->
<div class="finnish-network-card" style="margin-top: 30px;">
<h3>🐛 Debug Log & System Status</h3>
<p>Real-time debugging information and system status messages.</p>
<div style="margin-bottom: 15px;">
<button class="button button-secondary" onclick="clearDebugLog()">🗑️ Clear Log</button>
<button class="button button-secondary" onclick="testAJAXConnection()">🔌 Test AJAX</button>
<button class="button button-secondary" onclick="testDatabaseTables()">🗄️ Test Database</button>
<button class="button button-secondary" onclick="createMissingTables()" style="background: #f39c12;">🛠️ Fix Missing Tables</button>
<button class="button button-primary" onclick="manualTestComment()" style="background: #e74c3c;">🧪 Test Comment Generation</button>
<button class="button button-secondary" onclick="refreshDebugLog()">🔄 Refresh</button>
</div>
<div id="ai-debug-log" style="
background: #000;
color: #00ff00;
padding: 15px;
height: 300px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 12px;
border: 1px solid #333;
white-space: pre-wrap;
">
[<?php echo date('Y-m-d H:i:s'); ?>] AI Community Manager Debug Log initialized
[<?php echo date('Y-m-d H:i:s'); ?>] Plugin Status: <?php echo class_exists('AI_Community_Manager') ? 'LOADED' : 'NOT LOADED'; ?>
[<?php echo date('Y-m-d H:i:s'); ?>] Database Tables Check:
<?php
global $wpdb;
$tables_to_check = array('ai_personas', 'ai_comments_history', 'ai_conversation_memory', 'ai_community_settings', 'ai_learning_data');
foreach ($tables_to_check as $table) {
$full_table_name = $wpdb->prefix . $table;
$exists = $wpdb->get_var("SHOW TABLES LIKE '$full_table_name'") === $full_table_name;
echo "[" . date('Y-m-d H:i:s') . "] Table $table: " . ($exists ? 'EXISTS' : 'MISSING') . "\n";
}
?>
[<?php echo date('Y-m-d H:i:s'); ?>] WordPress Info:
[<?php echo date('Y-m-d H:i:s'); ?>] - WP Version: <?php echo get_bloginfo('version'); ?>
[<?php echo date('Y-m-d H:i:s'); ?>] - PHP Version: <?php echo PHP_VERSION; ?>
[<?php echo date('Y-m-d H:i:s'); ?>] - User Can Manage Options: <?php echo current_user_can('manage_options') ? 'YES' : 'NO'; ?>
[<?php echo date('Y-m-d H:i:s'); ?>] - AJAX URL: <?php echo admin_url('admin-ajax.php'); ?>
[<?php echo date('Y-m-d H:i:s'); ?>] Ready for testing...
</div>
</div>
<!-- JavaScript for AI Community Manager -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Update slider values in real-time
const dailySlider = document.getElementById('daily_comment_limit');
const dailyValue = document.getElementById('daily_limit_value');
const approvalSlider = document.getElementById('auto_approval_threshold');
const approvalValue = document.getElementById('approval_value');
const personasSlider = document.getElementById('max_personas');
const personasValue = document.getElementById('personas_value');
if (dailySlider && dailyValue) {
dailySlider.addEventListener('input', function() {
dailyValue.textContent = this.value;
});
}
if (approvalSlider && approvalValue) {
approvalSlider.addEventListener('input', function() {
approvalValue.textContent = this.value;
});
}
if (personasSlider && personasValue) {
personasSlider.addEventListener('input', function() {
personasValue.textContent = this.value;
});
}
// Select all checkbox functionality
const selectAll = document.getElementById('select-all-comments');
if (selectAll) {
selectAll.addEventListener('change', function() {
const checkboxes = document.querySelectorAll('.comment-checkbox');
checkboxes.forEach(cb => cb.checked = this.checked);
});
}
});
function approveComment(commentId) {
if (confirm('Approve this AI comment for posting?')) {
fetch(ajaxurl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'ai_community_action',
ai_action: 'approve_comment',
comment_id: commentId,
nonce: '<?php echo wp_create_nonce('ai_community_nonce'); ?>'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Error: ' + data.message);
}
});
}
}
function rejectComment(commentId) {
if (confirm('Reject this AI comment? This will help the AI learn what you don\'t want.')) {
fetch(ajaxurl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'ai_community_action',
ai_action: 'reject_comment',
comment_id: commentId,
nonce: '<?php echo wp_create_nonce('ai_community_nonce'); ?>'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Error: ' + data.message);
}
});
}
}
function bulkApproveSelected() {
const selected = Array.from(document.querySelectorAll('.comment-checkbox:checked'))
.map(cb => cb.dataset.id);
if (selected.length === 0) {
alert('Please select comments to approve.');
return;
}
if (confirm(`Approve ${selected.length} selected comments?`)) {
fetch(ajaxurl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'ai_community_action',
ai_action: 'bulk_approve',
comment_ids: JSON.stringify(selected),
nonce: '<?php echo wp_create_nonce('ai_community_nonce'); ?>'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Error: ' + data.message);
}
});
}
}
function bulkRejectSelected() {
const selected = Array.from(document.querySelectorAll('.comment-checkbox:checked'))
.map(cb => cb.dataset.id);
if (selected.length === 0) {
alert('Please select comments to reject.');
return;
}
if (confirm(`Reject ${selected.length} selected comments? This helps AI learn your preferences.`)) {
debugLog('📤 Bulk rejecting comments: ' + selected.join(', '));
fetch(ajaxurl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'ai_community_action',
ai_action: 'bulk_reject',
comment_ids: JSON.stringify(selected),
nonce: '<?php echo wp_create_nonce('ai_community_nonce'); ?>'
})
})
.then(response => response.json())
.then(data => {
debugLog('📥 Bulk reject response:', data);
if (data.success) {
alert(data.message);
location.reload();
} else {
alert('Error: ' + (data.message || 'Unknown error occurred'));
}
})
.catch(error => {
debugLog('❌ Bulk reject error:', error);
alert('Network error occurred. Please try again.');
});
}
}
function generateNewPersona() {
debugLog('🤖 Generate Persona button clicked');
if (confirm('Generate a new AI persona? This will create a unique commenter with random characteristics.')) {
debugLog('📤 Sending persona generation request...');
const requestData = {
action: 'ai_community_action',
ai_action: 'generate_persona',
nonce: '<?php echo wp_create_nonce('ai_community_nonce'); ?>'
};
debugLog('📋 Request data: ' + JSON.stringify(requestData));
fetch(ajaxurl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams(requestData)
})
.then(response => {
debugLog('📨 Response status: ' + response.status);
debugLog('📨 Response headers: ' + JSON.stringify([...response.headers.entries()]));
return response.text().then(text => {
debugLog('📨 Raw response: ' + text);
try {
return JSON.parse(text);
} catch (e) {
debugLog('❌ JSON parse error: ' + e.message);
throw new Error('Invalid JSON response: ' + text);
}
});
})
.then(data => {
debugLog('📨 Parsed response: ' + JSON.stringify(data));
if (data.success) {
debugLog('✅ Persona created successfully: ' + data.persona.display_name);
alert(`New persona created: ${data.persona.display_name}`);
location.reload();
} else {
debugLog('❌ Persona creation failed: ' + data.message);
alert('Error creating persona: ' + data.message);
}
})
.catch(error => {
debugLog('💥 Fetch error: ' + error.message);
alert('Network error: ' + error.message);
});
} else {
debugLog('❌ User cancelled persona generation');
}
}
function saveAISettings() {
debugLog('💾 Save Settings button clicked');
// Collect settings from form
const settings = {
enabled: document.getElementById('ai_community_enabled').checked,
daily_limit: document.getElementById('daily_comment_limit').value,
auto_approval: document.getElementById('auto_approval_threshold').value,
max_personas: document.getElementById('max_personas').value,
quality_level: document.getElementById('comment_quality_level').value,
randomize_quality: document.getElementById('randomize_quality').checked,
persona_diversity: document.getElementById('persona_diversity').value,
engagement_style: document.getElementById('engagement_style').value,
comment_language: document.getElementById('comment_language').value,
manual_approval: document.getElementById('require_manual_approval').checked,
detection_sensitivity: document.getElementById('detection_sensitivity').value,
learning_enabled: document.getElementById('learning_enabled').checked
};
debugLog('📋 Settings to save: ' + JSON.stringify(settings, null, 2));
const requestData = {
action: 'ai_community_action',
ai_action: 'update_settings',
settings: JSON.stringify(settings),
nonce: '<?php echo wp_create_nonce('ai_community_nonce'); ?>'
};
debugLog('📤 Sending settings update request...');
fetch(ajaxurl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams(requestData)
})
.then(response => {
debugLog('📨 Settings response status: ' + response.status);
return response.text().then(text => {
debugLog('📨 Settings raw response: ' + text);
try {
return JSON.parse(text);
} catch (e) {
debugLog('❌ Settings JSON parse error: ' + e.message);
throw new Error('Invalid JSON response: ' + text);
}
});
})
.then(data => {
debugLog('📨 Settings parsed response: ' + JSON.stringify(data));
if (data.success) {
debugLog('✅ Settings saved successfully');
alert('AI Community settings saved successfully!');
} else {
debugLog('❌ Settings save failed: ' + data.message);
alert('Error saving settings: ' + data.message);
}
})
.catch(error => {
debugLog('💥 Settings save error: ' + error.message);
alert('Network error saving settings: ' + error.message);
});
}
document.getElementById('emergency_stop_ai')?.addEventListener('click', function() {
if (confirm('EMERGENCY STOP: This will immediately halt ALL AI Community activity. Are you sure?')) {
fetch(ajaxurl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'ai_community_action',
ai_action: 'emergency_stop',
nonce: '<?php echo wp_create_nonce('ai_community_nonce'); ?>'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Emergency stop activated. All AI activity has been halted.');
location.reload();
}
});
}
});
// Debug logging functions
function debugLog(message) {
const logElement = document.getElementById('ai-debug-log');
if (logElement) {
const timestamp = new Date().toISOString().slice(0, 19);
logElement.innerHTML += '[' + timestamp + '] ' + message + '\n';
logElement.scrollTop = logElement.scrollHeight;
}
console.log('[AI Community] ' + message);
}
function clearDebugLog() {
const logElement = document.getElementById('ai-debug-log');
if (logElement) {
logElement.innerHTML = '[' + new Date().toISOString().slice(0, 19) + '] Debug log cleared\n';
}
debugLog('🗑️ Log cleared by user');
}
function testAJAXConnection() {
debugLog('🔌 Testing AJAX connection...');
fetch(ajaxurl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'ai_community_action',
ai_action: 'test_connection',
nonce: '<?php echo wp_create_nonce('ai_community_nonce'); ?>'
})
})
.then(response => {
debugLog('🔌 AJAX test response status: ' + response.status);
return response.text();
})
.then(text => {
debugLog('🔌 AJAX test response: ' + text);
try {
const data = JSON.parse(text);
debugLog('✅ AJAX connection working: ' + JSON.stringify(data));
} catch (e) {
debugLog('❌ AJAX response not JSON: ' + text);
}
})
.catch(error => {
debugLog('❌ AJAX connection failed: ' + error.message);
});
}
function testDatabaseTables() {
debugLog('🗄️ Testing database tables...');
fetch(ajaxurl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'ai_community_action',
ai_action: 'test_database',
nonce: '<?php echo wp_create_nonce('ai_community_nonce'); ?>'
})
})
.then(response => response.text())
.then(text => {
debugLog('🗄️ Database test response: ' + text);
try {
const data = JSON.parse(text);
debugLog('✅ Database test results: ' + JSON.stringify(data, null, 2));
} catch (e) {
debugLog('❌ Database test response not JSON: ' + text);
}
})
.catch(error => {
debugLog('❌ Database test failed: ' + error.message);
});
}
function createMissingTables() {
debugLog('🛠️ Creating missing database tables...');
fetch(ajaxurl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'ai_community_action',
ai_action: 'create_missing_tables',
nonce: '<?php echo wp_create_nonce('ai_community_nonce'); ?>'
})
})
.then(response => response.text())
.then(text => {
try {
const data = JSON.parse(text);
if (data.success) {
debugLog('✅ Tables created successfully!');
debugLog('📊 Before: ' + data.before + ' healthy tables');
debugLog('📊 After: ' + data.after + ' healthy tables');
debugLog('📝 Result: ' + data.message);
} else {
debugLog('❌ Table creation failed: ' + data.message);
if (data.table_details) {
debugLog('📊 Table details: ' + JSON.stringify(data.table_details, null, 2));
}
}
} catch (e) {
debugLog('❌ Table creation response not JSON: ' + text);
}
})
.catch(error => {
debugLog('❌ Table creation failed: ' + error.message);
});
}
function manualTestComment() {
debugLog('🧪 Testing AI comment generation manually...');
fetch(ajaxurl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'ai_community_action',
ai_action: 'manual_test_comment',
nonce: '<?php echo wp_create_nonce('ai_community_nonce'); ?>'
})
})
.then(response => response.text())
.then(text => {
try {
const data = JSON.parse(text);
if (data.success) {
debugLog('✅ Comment generation test successful!');
debugLog('📝 Result: ' + data.message);
if (data.post_title) {
debugLog('📄 Post: ' + data.post_title + ' (ID: ' + data.post_id + ')');
}
if (data.opportunities_found) {
debugLog('🎯 Opportunities found: ' + data.opportunities_found);
}
} else {
debugLog('❌ Comment generation failed: ' + data.message);
if (data.table_status) {
debugLog('📊 Table status: ' + JSON.stringify(data.table_status, null, 2));
}
}
} catch (e) {
debugLog('❌ Comment test response not JSON: ' + text);
}
})
.catch(error => {
debugLog('❌ Comment test failed: ' + error.message);
});
}
function refreshDebugLog() {
debugLog('🔄 Refreshing debug information...');
location.reload();
}
// Log when page loads
document.addEventListener('DOMContentLoaded', function() {
debugLog('🚀 AI Community Manager dashboard loaded');
debugLog('🌐 AJAX URL: ' + ajaxurl);
debugLog('🔑 Nonce: <?php echo wp_create_nonce('ai_community_nonce'); ?>');
debugLog('👤 User can manage options: <?php echo current_user_can('manage_options') ? 'YES' : 'NO'; ?>');
});
</script>
<?php elseif ( $active_tab == 'help' ) : ?>
<!-- HELP & FAQ TAB -->
<div class="finnish-network-card">
<h2>📚 Advanced Content Scheduling Aphex - Complete Guide</h2>
<h3>🚀 Step-by-Step Setup (15 Minutes)</h3>
<h4>Step 1: API Keys Setup (5 minutes)</h4>
<ol>
<li><strong>OpenAI API Key</strong> (Required for content generation):
<ul>
<li>Go to <a href="https://platform.openai.com/" target="_blank">platform.openai.com</a></li>
<li>Sign in/create account → API Keys → Create new secret key</li>
<li>Copy key → Settings tab → Paste in "OpenAI API Key" field</li>
<li>Add credits to your OpenAI account ($5-10 recommended for testing)</li>
</ul>
</li>
<li><strong>Perplexity API Key</strong> (Required for fact-checking):
<ul>
<li>Go to <a href="https://www.perplexity.ai/" target="_blank">perplexity.ai</a> → Sign up</li>
<li>Visit API section → Generate API key</li>
<li>Settings tab → Paste in "Perplexity API Key" field</li>
<li>Add credits ($10 minimum for Pro access)</li>
</ul>
</li>
<li><strong>Pexels API Key</strong> (Free, for stock images):
<ul>
<li>Go to <a href="https://www.pexels.com/api/" target="_blank">pexels.com/api</a></li>
<li>Create free account → Generate API key</li>
<li>Settings tab → Paste in "Pexels API Key" field</li>
</ul>
</li>
<li><strong>Target Language</strong>: Set to your content language (e.g., "English", "Spanish")</li>
</ol>
<h4>Step 2: Choose Your Content Source (3 minutes)</h4>
<p><strong>Option A: RSS Feeds (Automated)</strong></p>
<ul>
<li>Feeds tab → Add RSS Feed → Enter feed URL (e.g., techcrunch.com/feed)</li>
<li>Test feed → Save</li>
<li>Best for: News sites, blogs with regular updates</li>
</ul>
<p><strong>Option B: URL/Sitemap Processing (Manual/Bulk)</strong></p>
<ul>
<li>URL Rewriter tab → Add individual URLs OR import from sitemap</li>
<li>Supports XML/TXT sitemaps and sitemap index files</li>
<li>Best for: Curated content, specific articles</li>
</ul>
<p><strong>Option C: Category Scraping (Auto-discovery)</strong></p>
<ul>
<li>Category Scraper tab → Add website category URLs</li>
<li>Auto-discovers articles from category pages</li>
<li>Best for: Bulk processing from news category feeds</li>
</ul>
<h4>Step 3: Create Your First Scheduler (5 minutes)</h4>
<ol>
<li><strong>Go to Scheduler tab</strong> → "Add New Scheduler"</li>
<li><strong>Basic Settings:</strong>
<ul>
<li>Name: "Daily Tech News" (or your choice)</li>
<li>Type: Recurring (Daily Forever)</li>
<li>Posts per Day: 3 (start small)</li>
<li>Content Source: RSS Feeds, URL Rewriter, or Category Scraper</li>
<li>Feed/Source: Select your RSS feed or source</li>
</ul>
</li>
<li><strong>Advanced Settings:</strong>
<ul>
<li>Time Strategy: Random Spread (09:00 - 21:00)</li>
<li>Category: Auto-Detect (AI) or choose specific</li>
<li>Author: Random/Current Author</li>
<li>Status: Active</li>
</ul>
</li>
<li><strong>Enhancement Features:</strong>
<ul>
<li>✓ Enable Perplexity Facts Enhancement</li>
<li>✓ Enable Perplexity Authority Links</li>
<li>Source Filtering: Manual, Bulk, Sitemap (choose what applies)</li>
</ul>
</li>
<li><strong>Save Scheduler</strong></li>
</ol>
<h4>Step 4: Enable 24/7 Automation (2 minutes)</h4>
<ol>
<li><strong>Settings tab</strong> → Cron Job Configuration</li>
<li><strong>Click "Auto-Setup Cron Job"</strong> (creates external cron job)</li>
<li><strong>Verify:</strong> Status should show "Cron Job Active"</li>
<li><strong>Alternative:</strong> Manual cron job setup with provided URL</li>
</ol>
<h4>Step 5: Test Your Setup (NOW!)</h4>
<ol>
<li><strong>Scheduler tab</strong> → Find your scheduler → Click "Run Now"</li>
<li><strong>Wait 1-2 minutes</strong> for AI processing</li>
<li><strong>Check Dashboard tab</strong> → Should see new posts in "Scheduled" section</li>
<li><strong>Check Logs tab</strong> → Should see "Scheduler completed" messages</li>
<li><strong>Success indicators:</strong> Green log messages, scheduled posts visible</li>
</ol>
<hr>
<h3>🎯 Content Modules Explained</h3>
<h4>📰 RSS Scheduler</h4>
<ul>
<li><strong>What it does:</strong> Monitors RSS feeds, processes new articles with AI enhancement</li>
<li><strong>Features:</strong> Auto-fetching, duplicate detection, smart scheduling</li>
<li><strong>Best for:</strong> News sites, blogs, automated daily content</li>
<li><strong>Setup:</strong> Feeds tab → Add RSS feeds → Create RSS scheduler</li>
</ul>
<h4>🔗 URL Rewriter</h4>
<ul>
<li><strong>What it does:</strong> Processes specific URLs with full AI enhancement suite</li>
<li><strong>Features:</strong> Sitemap import, source tracking, bulk management, pagination</li>
<li><strong>Best for:</strong> High-quality curated content, specific articles</li>
<li><strong>Setup:</strong> URL Rewriter tab → Add URLs/sitemaps → Create URL Rewriter scheduler</li>
</ul>
<h4>🌐 Category Scraper</h4>
<ul>
<li><strong>What it does:</strong> Auto-discovers articles from category pages, processes with AI</li>
<li><strong>Features:</strong> Auto-discovery, source management, same enhancements as URL Rewriter</li>
<li><strong>Best for:</strong> Bulk processing from category feeds, discovering new content</li>
<li><strong>Setup:</strong> Category Scraper tab → Add category URLs → Create Category Scraper scheduler</li>
</ul>
<h4>♻️ Evergreen Reviver</h4>
<ul>
<li><strong>What it does:</strong> Refreshes old posts (180+ days) with new AI-generated content</li>
<li><strong>Features:</strong> Age-based selection, content preservation, SEO maintenance</li>
<li><strong>Best for:</strong> Infinite content generation, updating old content</li>
<li><strong>Setup:</strong> Create Evergreen Reviver scheduler (no content source needed)</li>
</ul>
<hr>
<h3>🎛️ Advanced Features Breakdown</h3>
<h4>🤖 AI Content Rewriting</h4>
<ul>
<li><strong>Model:</strong> GPT-4 Turbo for high-quality content transformation</li>
<li><strong>Process:</strong> Rewrites content in your target language while preserving facts</li>
<li><strong>Quality:</strong> Maintains readability, improves structure, adds context</li>
</ul>
<h4>🧠 Perplexity Fact-Checking & Authority Links</h4>
<ul>
<li><strong>Smart Integration:</strong> Adds authority links naturally within content</li>
<li><strong>Fact Enhancement:</strong> Weaves additional facts into articles</li>
<li><strong>Settings Control:</strong> Configure number of inline links (0-10) and footnotes (0-10)</li>
<li><strong>Quality:</strong> Creates grammatical anchor text, relevant connections only</li>
</ul>
<h4>🖼️ Smart Image Generation</h4>
<ul>
<li><strong>AI Images:</strong> DALL-E 3 generates contextual images (1792x1024, $0.08 each)</li>
<li><strong>Stock Images:</strong> Pexels provides free professional photos</li>
<li><strong>Strategies:</strong> AI-first, Pexels-first, or RSS-first with fallbacks</li>
<li><strong>SEO Optimized:</strong> Proper alt text, contextual relevance</li>
</ul>
<h4>🔗 Internal Linking System</h4>
<ul>
<li><strong>Content Relevance:</strong> Links only to posts with 15%+ content similarity</li>
<li><strong>Smart Anchors:</strong> Creates contextual anchor text from actual content</li>
<li><strong>Quality Control:</strong> Prevents irrelevant or weak connections</li>
</ul>
<h4>✅ SEO Optimization</h4>
<ul>
<li><strong>Title Optimization:</strong> Intelligent truncation preserving meaning (70 chars)</li>
<li><strong>Meta Descriptions:</strong> AI-generated, keyword-optimized (150-160 chars)</li>
<li><strong>SEO Plugin Support:</strong> Works with Yoast, RankMath, All-in-One SEO</li>
<li><strong>Duplicate Prevention:</strong> Title similarity (85%) and content checking</li>
</ul>
<hr>
<h3>⚙️ Complete Settings Guide</h3>
<h4>🔑 Essential API Keys</h4>
<ul>
<li><strong>License Key:</strong> Required for plugin activation - get from your purchase receipt or vendor dashboard</li>
<li><strong>OpenAI API Key:</strong> Powers all content rewriting and AI image generation (required for all modules)</li>
<li><strong>Target Language:</strong> Critical setting - determines content output language (e.g., "English", "Spanish", "French")</li>
<li><strong>Perplexity API Key:</strong> Enables fact-checking and authority link insertion (required for enhancement features)</li>
</ul>
<h4>🔧 Optional Enhancement Keys</h4>
<ul>
<li><strong>Pexels API Key:</strong> Free stock images with 20,000 requests/month limit (recommended for cost savings)</li>
<li><strong>ScraperAPI Key:</strong> Improves scraping success for difficult/protected sites (optional but helpful)</li>
<li><strong>DataForSEO API Key:</strong> Advanced scraping capabilities for complex websites (optional premium feature)</li>
<li><strong>Slack Webhook URL:</strong> Get notifications when posts are published or errors occur (optional monitoring)</li>
</ul>
<h4>🖼️ Image Generation Settings</h4>
<ul>
<li><strong>AI-First + Pexels Fallback:</strong> Tries DALL-E 3 first, falls back to Pexels ($0.08 per AI image)</li>
<li><strong>Pexels-First + AI Fallback:</strong> Tries free Pexels first, uses AI if no match found</li>
<li><strong>Pexels Only:</strong> Free stock images only (no AI generation costs)</li>
<li><strong>Disabled:</strong> No images generated (faster processing, zero image costs)</li>
</ul>
<h4>🧠 Perplexity Enhancement Settings</h4>
<ul>
<li><strong>Perplexity Model:</strong> Choose Sonar (faster) or Sonar Pro (more accurate)</li>
<li><strong>Contextual Facts:</strong> Number of facts to weave into content (0-5)</li>
<li><strong>Inline Links:</strong> Authority links placed within content (0-10)</li>
<li><strong>Footnote Links:</strong> Reference links at article bottom (0-10)</li>
<li><strong>Link Style:</strong> Contextual only, footnote only, or both</li>
</ul>
<h4>✅ Quality Control Settings</h4>
<ul>
<li><strong>Enable SEO Validation:</strong> Optimizes titles (70 chars) and meta descriptions (155 chars)</li>
<li><strong>Enable Duplicate Prevention:</strong> Blocks content with 85% title similarity or database matches</li>
<li><strong>Auto-Publish Quality Posts:</strong> Auto-publishes posts scoring ≥60% quality</li>
<li><strong>Duplicate Action:</strong> Move duplicates to Draft, Trash, or Delete permanently</li>
</ul>
<h4>⏰ 24/7 Automation Setup (Critical for Reliable Operation)</h4>
<p><strong>Why You Need This:</strong> WordPress's built-in scheduling only works when people visit your site. For true 24/7 content generation, you need external automation that runs regardless of site traffic.</p>
<h5>🚀 Option 1: Auto-Setup (Easiest - Recommended)</h5>
<ul>
<li><strong>One-Click Setup:</strong> Click "Auto-Setup Cron Job" and the plugin handles everything</li>
<li><strong>External Service:</strong> Creates reliable external cron job that pings your site hourly</li>
<li><strong>Status Verification:</strong> Green "Cron Job Active" means it's working correctly</li>
<li><strong>Zero Maintenance:</strong> Once setup, runs forever without intervention</li>
</ul>
<h5>🔧 Option 2: Manual External Cron (Most Reliable)</h5>
<ul>
<li><strong>Copy URL:</strong> Plugin provides your unique cron URL in Settings</li>
<li><strong>Cron-Job.org:</strong> Free service - paste URL, set to run every hour</li>
<li><strong>Hosting Panel:</strong> Most hosts offer cron job setup in control panel</li>
<li><strong>Server Cron:</strong> Add to server crontab: <code>0 * * * * wget -q -O- [your-url]</code></li>
</ul>
<h5>⚠️ Option 3: WordPress Cron (Not Recommended)</h5>
<ul>
<li><strong>Visitor Dependent:</strong> Only runs when someone visits your site</li>
<li><strong>Unreliable Timing:</strong> May miss scheduled times during low traffic</li>
<li><strong>Use Case:</strong> Only suitable for high-traffic sites with constant visitors</li>
<li><strong>Backup Option:</strong> Better than nothing if external cron isn't possible</li>
</ul>
<h5>📊 Cron Job Troubleshooting</h5>
<ul>
<li><strong>"Cron Job Failed":</strong> Check if your hosting blocks external requests</li>
<li><strong>Missed Schedules:</strong> Verify cron job is actually running hourly</li>
<li><strong>Status Check:</strong> Visit your cron URL manually - should show "Cron executed successfully"</li>
<li><strong>Alternative Services:</strong> Try UptimeRobot, EasyCron, or SetCronJob if auto-setup fails</li>
</ul>
<hr>
<h3>🔗 URL Rewriter Complete Guide</h3>
<h4>📝 URL Input Methods</h4>
<ul>
<li><strong>Single URL:</strong> Add individual articles manually with "manual" source tag</li>
<li><strong>Bulk URLs:</strong> Paste multiple URLs (one per line) with "bulk" source tag</li>
<li><strong>Sitemap Import:</strong> Import entire sitemaps (XML/TXT) with "sitemap" source tag</li>
<li><strong>Source Tracking:</strong> All URLs tagged by input method for scheduler filtering</li>
</ul>
<h4>📊 Queue Management</h4>
<ul>
<li><strong>Pending URLs Table:</strong> Shows unprocessed URLs with source, priority, and date</li>
<li><strong>Pagination:</strong> 20 URLs per page for performance (navigate with Previous/Next)</li>
<li><strong>Bulk Actions:</strong> Select multiple URLs to delete, change priority, or mark processing</li>
<li><strong>Priority System:</strong> High priority URLs processed first</li>
</ul>
<h4>🎯 Sitemap Sources Management</h4>
<ul>
<li><strong>Active Sitemaps:</strong> Table shows imported sitemaps with URL count and date</li>
<li><strong>Source Deletion:</strong> Removing sitemap also removes all its pending URLs</li>
<li><strong>Sitemap Types:</strong> Supports XML sitemaps, TXT sitemaps, and sitemap index files</li>
<li><strong>Auto-Detection:</strong> Handles nested sitemaps and large sitemap files</li>
</ul>
<h4>📈 Processing Status</h4>
<ul>
<li><strong>Processed URLs:</strong> Shows completed articles with post links and processing date</li>
<li><strong>Failed URLs:</strong> Lists URLs that couldn't be processed with error reasons</li>
<li><strong>Status Tracking:</strong> Real-time updates on queue processing progress</li>
<li><strong>Cleanup Tools:</strong> Remove failed URLs or reset processing status</li>
</ul>
<h4>⚙️ URL Rewriter Settings</h4>
<ul>
<li><strong>Enhancement Features:</strong> Enable/disable Perplexity, LSI keywords, internal linking, images</li>
<li><strong>Processing Limits:</strong> Set daily processing limits to control API costs</li>
<li><strong>Priority Handling:</strong> Configure how priority URLs are processed</li>
<li><strong>Error Handling:</strong> Set retry attempts and failure actions</li>
</ul>
<hr>
<h3>🌐 Category Scraper Complete Guide</h3>
<h4>🔍 Source Discovery</h4>
<ul>
<li><strong>Category URLs:</strong> Add website category/section pages (e.g., site.com/tech-news/)</li>
<li><strong>Auto-Discovery:</strong> Plugin finds article links on category pages automatically</li>
<li><strong>Frequency Settings:</strong> Set how often to check each category for new articles</li>
<li><strong>Article Limits:</strong> Control how many articles to extract per category scan</li>
</ul>
<h4>📋 Configured Sources</h4>
<ul>
<li><strong>Source Management:</strong> Table shows active category sources with last scan date</li>
<li><strong>Source Status:</strong> Active/paused sources with success rates</li>
<li><strong>Article Count:</strong> Shows total articles discovered per source</li>
<li><strong>Source Removal:</strong> Delete sources and their discovered articles</li>
</ul>
<h4>🎛️ Category Scraper Settings</h4>
<ul>
<li><strong>Same as URL Rewriter:</strong> Perplexity, LSI keywords, internal linking, AI images</li>
<li><strong>Discovery Frequency:</strong> How often to scan category pages (hourly/daily/weekly)</li>
<li><strong>Content Filters:</strong> Minimum article length, publish date restrictions</li>
<li><strong>Processing Strategy:</strong> Process immediately or add to queue</li>
</ul>
<h4>📊 Processing Overview</h4>
<ul>
<li><strong>Processed Articles:</strong> Shows articles created from category scraping</li>
<li><strong>Success Rate:</strong> Percentage of successfully processed vs failed articles</li>
<li><strong>Source Performance:</strong> Which category sources generate the best content</li>
<li><strong>Error Tracking:</strong> Failed URLs with specific error reasons</li>
</ul>
<hr>
<h3>📅 Scheduler Advanced Configuration</h3>
<h4>🔄 Scheduler Types Deep Dive</h4>
<ul>
<li><strong>Recurring Daily:</strong> Runs every day at calculated intervals forever</li>
<li><strong>Recurring Weekly:</strong> Runs weekly on specified days with daily distribution</li>
<li><strong>Date Range:</strong> Runs daily between start/end dates then stops automatically</li>
<li><strong>Specific Dates:</strong> Runs only on manually selected dates with custom post counts</li>
</ul>
<h4>⏰ Time Management</h4>
<ul>
<li><strong>Random Spread:</strong> Distributes posts randomly within time window (most natural)</li>
<li><strong>Fixed Intervals:</strong> Posts at exact time intervals (e.g., every 4 hours)</li>
<li><strong>Peak Hours:</strong> Concentrates posts during high-traffic periods</li>
<li><strong>Time Zone:</strong> Uses WordPress timezone setting for all scheduling</li>
</ul>
<h4>🎯 Content Source Configuration</h4>
<ul>
<li><strong>RSS Feeds:</strong> Select specific feeds or "All Active Feeds"</li>
<li><strong>URL Rewriter:</strong> Process from queue with source filtering options</li>
<li><strong>Category Scraper:</strong> Use discovered articles from active category sources</li>
<li><strong>Evergreen Reviver:</strong> Target posts older than specified days (minimum 180)</li>
</ul>
<h4>🔍 Source Filtering (URL Rewriter/Category Scraper)</h4>
<ul>
<li><strong>Manual URLs:</strong> Process only manually added individual URLs</li>
<li><strong>Bulk URLs:</strong> Process only URLs added via bulk import</li>
<li><strong>Sitemap URLs:</strong> Process only URLs imported from sitemaps</li>
<li><strong>Multiple Selection:</strong> Combine filters (e.g., manual + sitemap only)</li>
</ul>
<h4>⚡ Enhancement Controls (Per Scheduler)</h4>
<ul>
<li><strong>Perplexity Facts:</strong> Enable/disable fact enhancement for this scheduler</li>
<li><strong>Authority Links:</strong> Enable/disable Perplexity link insertion</li>
<li><strong>Override Global:</strong> Scheduler settings override global Perplexity settings</li>
<li><strong>Granular Control:</strong> Different enhancement levels per content type</li>
</ul>
<h4>📊 Scheduler Monitoring</h4>
<ul>
<li><strong>Next Run Time:</strong> Exact date/time of next scheduled execution</li>
<li><strong>Success Rate:</strong> Percentage of successful vs failed content generation</li>
<li><strong>Daily Progress:</strong> Posts generated today vs daily limit</li>
<li><strong>Calendar View:</strong> Visual representation of scheduled content</li>
</ul>
<hr>
<h3>🚨 Troubleshooting Guide</h3>
<h4>❌ "No posts generating"</h4>
<ul>
<li><strong>Check API Keys:</strong> Settings tab → verify all fields filled, test connection</li>
<li><strong>Check Credits:</strong> Ensure OpenAI/Perplexity accounts have sufficient credits</li>
<li><strong>Check Scheduler:</strong> Verify "Active" status, correct content source selected</li>
<li><strong>Check Target Language:</strong> Must be set (e.g., "English", not empty)</li>
<li><strong>Check Logs:</strong> Look for specific error messages in Logs tab</li>
</ul>
<h4>❌ "License Key Invalid" or "Plugin Not Activated"</h4>
<ul>
<li><strong>Check License:</strong> Verify license key from purchase receipt or vendor dashboard</li>
<li><strong>Domain Registration:</strong> Ensure your domain is registered with the license</li>
<li><strong>License Limit:</strong> Check if you've exceeded the allowed number of sites</li>
<li><strong>Renewal Required:</strong> Verify license hasn't expired and renew if necessary</li>
<li><strong>Contact Support:</strong> If all else fails, contact vendor support with purchase details</li>
</ul>
<h4>❌ "API Key Invalid" errors</h4>
<ul>
<li><strong>OpenAI:</strong> Regenerate key at platform.openai.com, update Settings</li>
<li><strong>Perplexity:</strong> Check account status, ensure Pro subscription active</li>
<li><strong>Pexels:</strong> Verify key copied correctly, check rate limits</li>
<li><strong>ScraperAPI:</strong> Confirm subscription active and credits available</li>
<li><strong>DataForSEO:</strong> Verify account status and API access enabled</li>
</ul>
<h4>❌ "Images not working"</h4>
<ul>
<li><strong>Strategy Check:</strong> Settings → Image Strategy not set to "Disabled"</li>
<li><strong>Pexels Key:</strong> Verify Pexels API key in Settings</li>
<li><strong>OpenAI Credits:</strong> DALL-E requires separate image credits</li>
<li><strong>Logs Check:</strong> Look for image generation errors in Logs tab</li>
</ul>
<h4>❌ "Meta descriptions not showing"</h4>
<ul>
<li><strong>Fixed:</strong> Latest version resolves meta description display issues</li>
<li><strong>Cache:</strong> Clear site cache, check view source for meta tags</li>
<li><strong>SEO Plugins:</strong> May override plugin meta descriptions</li>
</ul>
<h4>❌ "Cron job not working"</h4>
<ul>
<li><strong>Auto-Setup Failed:</strong> Try manual cron job setup with provided URL</li>
<li><strong>External Service:</strong> Use cron-job.org or similar service</li>
<li><strong>Server Limitations:</strong> Some hosts block external connections</li>
<li><strong>Alternative:</strong> WordPress cron (less reliable, visitor-dependent)</li>
</ul>
<h4>❌ "Duplicate content detected"</h4>
<ul>
<li><strong>Expected Behavior:</strong> Plugin prevents duplicate content for SEO</li>
<li><strong>Solution:</strong> Use "Fetch Fresh Content" in scheduler settings</li>
<li><strong>Alternative:</strong> Disable uniqueness check in module settings</li>
</ul>
<hr>
<h3>💡 Best Practices & Optimization</h3>
<h4>🚀 Performance Tips</h4>
<ul>
<li><strong>Start Small:</strong> Begin with 1-3 posts/day, increase gradually</li>
<li><strong>Monitor Costs:</strong> OpenAI: ~$1-3/month for 10 posts/day, Perplexity: ~$1/month</li>
<li><strong>Queue Management:</strong> Keep URL Rewriter queue under 100 URLs for performance</li>
<li><strong>Time Distribution:</strong> Use "Random Spread" to avoid posting patterns</li>
</ul>
<h4>📊 Monitoring & Maintenance</h4>
<ul>
<li><strong>Daily Checks:</strong> Dashboard (new posts), Scheduler (next run times), Logs (errors)</li>
<li><strong>Weekly Checks:</strong> API usage/costs, failed URLs in URL Rewriter</li>
<li><strong>Monthly Checks:</strong> Content quality review, scheduler performance analysis</li>
</ul>
<h4>⚙️ Advanced Configuration</h4>
<ul>
<li><strong>Multiple Schedulers:</strong> Create topic-specific schedulers for organized content</li>
<li><strong>Category Targeting:</strong> Use specific categories instead of AI auto-detect for consistency</li>
<li><strong>Source Filtering:</strong> Filter schedulers by manual/bulk/sitemap sources for control</li>
<li><strong>Enhancement Settings:</strong> Adjust Perplexity facts/links per scheduler needs</li>
</ul>
<hr>
<h3>🔍 Common Questions</h3>
<p><strong>Q: How much does it cost to run?</strong><br>
A: Example for 10 posts/day: OpenAI ~$1-3/month (including images), Perplexity ~$1/month, Pexels free, cron job free.</p>
<p><strong>Q: Does it work 24/7 without me being logged in?</strong><br>
A: Yes! Once cron job is set up, schedulers run automatically regardless of login status.</p>
<p><strong>Q: Can I use it with my existing SEO plugin?</strong><br>
A: Yes, fully compatible with Yoast SEO, RankMath, All-in-One SEO, and others.</p>
<p><strong>Q: What happens to my existing content?</strong><br>
A: Plugin only creates new content. Existing content remains untouched (except Evergreen Reviver module).</p>
<p><strong>Q: How do I ensure content quality?</strong><br>
A: Start with trusted RSS sources, use AI auto-categorization, enable Perplexity fact-checking, monitor Logs regularly.</p>
<p><strong>Q: Can I customize the writing style?</strong><br>
A: Yes, set target language in Settings. Content style adapts to your language and source material tone.</p>
<hr style="margin: 40px 0;">
<h2>🤖 AI Community Manager - Complete Guide</h2>
<h3>🎯 What is AI Community Manager?</h3>
<p>AI Community Manager is an advanced system that automatically generates <strong>human-like comments</strong> on your blog posts using sophisticated AI personas. Unlike generic chatbots, it creates <strong>diverse personalities</strong> that comment naturally, driving engagement and making your site feel more active and social.</p>
<h3>🧠 How It Works - The Process</h3>
<h4>Step 1: Post Analysis & Opportunity Detection</h4>
<ul>
<li><strong>Content Scanning:</strong> AI analyzes new and existing posts for engagement opportunities</li>
<li><strong>Topic Extraction:</strong> Identifies key themes, sentiment, and discussion potential</li>
<li><strong>Timing Analysis:</strong> Considers post age, existing comment activity, and optimal timing</li>
<li><strong>Engagement Score:</strong> Calculates likelihood of generating meaningful discussion (0-100%)</li>
</ul>
<h4>Step 2: Smart Decision Making</h4>
<p>The AI decides <strong>IF, WHEN, and HOW</strong> to engage:</p>
<ul>
<li><strong>Daily Limit Check:</strong> Respects your configured comment limit (e.g., 2 per day)</li>
<li><strong>Post Prioritization:</strong> Chooses posts with highest engagement potential</li>
<li><strong>Conversation Context:</strong> Decides whether to start new discussion or respond to existing comments</li>
<li><strong>Natural Timing:</strong> Spreads comments throughout day to avoid suspicious patterns</li>
</ul>
<h4>Step 3: Persona Selection & Comment Generation</h4>
<ul>
<li><strong>Persona Matching:</strong> Selects appropriate personality archetype for the topic</li>
<li><strong>Language Adaptation:</strong> Generates comments in your selected language with cultural expressions</li>
<li><strong>Quality Control:</strong> Applies quality level settings and archetype-specific traits</li>
<li><strong>Anti-AI Detection:</strong> Heavy penalties for corporate language, bonuses for natural expressions</li>
</ul>
<h3>🎭 Personality Archetypes - 12 Distinct Types</h3>
<p><strong>Each persona maintains consistent personality across all their comments, just like real people!</strong></p>
<h4>Core Personality Types:</h4>
<ul>
<li><strong>🤔 The Skeptic:</strong> "That sounds too good to be true... what's the catch?"</li>
<li><strong>🤩 The Enthusiast:</strong> "OMG this is amazing!! Can't wait to try it!"</li>
<li><strong>⚙️ The Practical:</strong> "But how does this actually work in real life? What are the steps?"</li>
<li><strong>📊 The Analytical:</strong> "Let me break this down... what's the methodology here?"</li>
<li><strong>😎 The Casual:</strong> "Meh, tried it, it's alright I guess. Nothing special."</li>
<li><strong>🤝 The Helper:</strong> "Here's a tip that might help! Try checking the settings first."</li>
</ul>
<h4>Variety & Humor Types:</h4>
<ul>
<li><strong>😂 The Comedian:</strong> "Well that escalated quickly! Next they'll tell us our toasters need AI 😂"</li>
<li><strong>😏 The Sarcastic:</strong> "Oh great, another 'revolutionary' solution. Because the last 47 worked so well..."</li>
<li><strong>📖 The Storyteller:</strong> "This reminds me of when I tried something similar back in 2019..."</li>
<li><strong>🤷 The Contrarian:</strong> "Devil's advocate here, but what about the counterargument that..."</li>
<li><strong>🆕 The Newbie:</strong> "Sorry if this is dumb but what exactly does that mean? Still learning here."</li>
<li><strong>🏆 The Veteran:</strong> "Been doing this for years. Pro tip: watch out for the hidden fees."</li>
</ul>
<h3>⚙️ Comment Quality Levels</h3>
<h4>🗨️ Ultra-Natural</h4>
<p><strong>Style:</strong> Imperfect grammar, typos, very casual like texting<br>
<strong>Example:</strong> "yeah tried this last week its pretty decent but idk about the pricing tho"</p>
<h4>💬 Natural (Default)</h4>
<p><strong>Style:</strong> Conversational with minor imperfections<br>
<strong>Example:</strong> "Tried this last week - it's pretty decent but not sure about the pricing"</p>
<h4>⚖️ Balanced</h4>
<p><strong>Style:</strong> Clean but still human-like<br>
<strong>Example:</strong> "I tried this recently and found it quite useful, though the pricing seems a bit steep."</p>
<h4>🧠 Informed</h4>
<p><strong>Style:</strong> Well-written with expertise showing<br>
<strong>Example:</strong> "From my experience testing similar solutions, this approach shows promise but requires careful consideration of the cost-benefit ratio."</p>
<h4>👔 Professional</h4>
<p><strong>Style:</strong> Polished but not corporate<br>
<strong>Example:</strong> "After evaluating this solution, I found the functionality solid, though the pricing structure warrants careful analysis for budget-conscious users."</p>
<h3>🎲 Randomization & Consistency</h3>
<h4>Persona Consistency</h4>
<ul>
<li><strong>Permanent Archetype:</strong> Each generated persona keeps the same personality forever</li>
<li><strong>Consistent Traits:</strong> Same formality level, enthusiasm, skepticism across all comments</li>
<li><strong>Signature Phrases:</strong> Each archetype has unique vocabulary and expressions</li>
<li><strong>Behavioral Patterns:</strong> Skeptics always question, Helpers always offer advice, etc.</li>
</ul>
<h4>Quality Randomization (Optional)</h4>
<ul>
<li><strong>Base Setting:</strong> You set preferred quality level (e.g., Natural)</li>
<li><strong>Variation Range:</strong> System varies ±1 level for natural diversity</li>
<li><strong>Example:</strong> Natural base → some Ultra-Natural, some Natural, some Balanced</li>
<li><strong>Realistic Diversity:</strong> Mimics how real people write differently on different days</li>
</ul>
<h3>🎯 When & How It Responds</h3>
<h4>New Comment Triggers</h4>
<ul>
<li><strong>Fresh Posts:</strong> High-engagement articles published within last 7 days</li>
<li><strong>Topic Relevance:</strong> Posts matching persona expertise areas</li>
<li><strong>Discussion Opportunities:</strong> Articles with controversial points or question-worthy content</li>
<li><strong>Community Building:</strong> Posts that can benefit from initial engagement to encourage real user participation</li>
</ul>
<h4>Response Triggers (to existing comments)</h4>
<ul>
<li><strong>Direct Questions:</strong> When real users ask questions that match persona expertise</li>
<li><strong>Debate Topics:</strong> Controversial points where Contrarian archetype can add alternative perspective</li>
<li><strong>Help Requests:</strong> Problem-solving opportunities for Helper archetype</li>
<li><strong>Story Prompts:</strong> Topics that trigger Storyteller to share relevant experiences</li>
</ul>
<h4>Smart Avoidance</h4>
<ul>
<li><strong>Over-saturation:</strong> Won't comment if post already has high AI comment density</li>
<li><strong>Recent Activity:</strong> Avoids commenting too soon after previous AI comment</li>
<li><strong>Off-topic Posts:</strong> Skips content that doesn't match any persona expertise</li>
<li><strong>Sensitive Content:</strong> Built-in safety filters avoid controversial or inappropriate topics</li>
</ul>
<h3>🔧 Configuration & Settings</h3>
<h4>Daily Limits & Control</h4>
<ul>
<li><strong>Daily Comment Limit:</strong> Set max comments per day (e.g., 2, 5, 12)</li>
<li><strong>Auto-Approval Threshold:</strong> Comments above quality score get auto-approved</li>
<li><strong>Manual Review Threshold:</strong> Lower quality comments go to moderation queue</li>
<li><strong>Emergency Stop:</strong> Instant pause button for complete system shutdown</li>
</ul>
<h4>Language & Cultural Adaptation</h4>
<ul>
<li><strong>9 Languages Supported:</strong> English (US/UK), Spanish, German, French, Italian, Swedish, Finnish, Norwegian</li>
<li><strong>Cultural Names:</strong> Appropriate first/last names for each country</li>
<li><strong>Local Expressions:</strong> Country-specific slang and casual language</li>
<li><strong>Natural Flow:</strong> Comments feel like they're from native speakers</li>
</ul>
<h3>📊 Monitoring & Analytics</h3>
<h4>Real-Time Tracking</h4>
<ul>
<li><strong>Today's Activity:</strong> Comments posted today vs daily limit</li>
<li><strong>Pending Queue:</strong> Comments awaiting manual approval</li>
<li><strong>Active Personas:</strong> Number of AI personas currently active</li>
<li><strong>User Responses:</strong> Real user replies to AI comments (engagement success)</li>
</ul>
<h4>Performance Metrics</h4>
<ul>
<li><strong>Approval Rate:</strong> Percentage of AI comments that get approved</li>
<li><strong>Engagement Rate:</strong> How often real users respond to AI comments</li>
<li><strong>Quality Scores:</strong> Average quality rating of generated comments</li>
<li><strong>Response Time:</strong> How quickly system generates contextual comments</li>
</ul>
<h3>⚡ Best Practices</h3>
<h4>Optimal Setup</h4>
<ul>
<li><strong>Start Small:</strong> Begin with 2-3 comments/day, increase gradually</li>
<li><strong>Quality Over Quantity:</strong> Better to have 2 great comments than 5 mediocre ones</li>
<li><strong>Monitor Engagement:</strong> Check if real users are responding to AI comments</li>
<li><strong>Regular Review:</strong> Periodically check pending queue for quality control</li>
</ul>
<h4>Natural Community Building</h4>
<ul>
<li><strong>Varied Timing:</strong> Let system spread comments naturally throughout the day</li>
<li><strong>Mixed Personas:</strong> Don't manually favor specific archetypes - let variety happen</li>
<li><strong>Encourage Real Users:</strong> AI comments should prompt real user participation, not replace it</li>
<li><strong>Authentic Engagement:</strong> Avoid obvious AI patterns that might seem fake to visitors</li>
</ul>
<h3>🚨 Troubleshooting</h3>
<h4>❌ "Comments not generating"</h4>
<ul>
<li><strong>Check Daily Limit:</strong> May have reached daily comment quota</li>
<li><strong>Post Analysis:</strong> AI might not find suitable engagement opportunities</li>
<li><strong>Emergency Stop:</strong> Check if system is paused</li>
<li><strong>Database Tables:</strong> Run "Manual Test Comment" to verify system health</li>
</ul>
<h4>❌ "Comments too robotic"</h4>
<ul>
<li><strong>Quality Level:</strong> Lower to "Natural" or "Ultra-Natural"</li>
<li><strong>Enable Randomization:</strong> Allows ±1 level variation for diversity</li>
<li><strong>Check Language:</strong> Ensure correct language selected for natural expressions</li>
<li><strong>Archetype Distribution:</strong> System automatically varies personality types</li>
</ul>
<h4>❌ "Too many comments pending approval"</h4>
<ul>
<li><strong>Adjust Thresholds:</strong> Lower auto-approval threshold to reduce manual review queue</li>
<li><strong>Quality Settings:</strong> Increase base quality level to improve automatic scores</li>
<li><strong>Review Process:</strong> Regularly approve/reject pending comments for system learning</li>
</ul>
<h3>💡 Pro Tips</h3>
<ul>
<li><strong>Persona Diversity:</strong> Each generated persona is permanently unique - no two personalities are identical</li>
<li><strong>Cultural Matching:</strong> Comments automatically adapt to your site's language and cultural context</li>
<li><strong>Learning System:</strong> Quality scores improve over time as AI learns your approval patterns</li>
<li><strong>Cost Efficient:</strong> Minimal OpenAI usage compared to content generation - typically under $1/month</li>
<li><strong>SEO Benefit:</strong> Natural user-generated content signals and increased engagement metrics</li>
<li><strong>Real Engagement:</strong> AI comments often prompt genuine user responses, creating authentic discussions</li>
</ul>
<p><strong>Plugin Version:</strong> 2.1.55 | <strong>Last Updated:</strong> December 2025</p>
</div>
<?php elseif ( $active_tab == 'url_rewriter' ) : ?>
<!-- URL REWRITER TAB -->
<div class="finnish-network-card">
<h2>🔗 URL Rewriter Module</h2>
<p>Process URLs from your queue, scrape content, and rewrite it with AI enhancement.</p>
<!-- URL Rewriter Settings -->
<h3>🔧 Settings</h3>
<form method="post" action="options.php">
<?php settings_fields( 'finnish_global_url_rewriter_options' ); ?>
<table class="form-table">
<tr>
<th scope="row">Enhancement Features</th>
<td>
<label><input type="checkbox" name="url_rewriter_enable_perplexity" value="1" <?php checked( get_option( 'url_rewriter_enable_perplexity', 1 ), 1 ); ?> /> Perplexity Fact-Checking</label><br>
<label><input type="checkbox" name="url_rewriter_enable_lsi" value="1" <?php checked( get_option( 'url_rewriter_enable_lsi', 1 ), 1 ); ?> /> LSI Keywords</label><br>
<label><input type="checkbox" name="url_rewriter_enable_internal_linking" value="1" <?php checked( get_option( 'url_rewriter_enable_internal_linking', 1 ), 1 ); ?> /> Internal Linking</label><br>
<label><input type="checkbox" name="url_rewriter_enable_images" value="1" <?php checked( get_option( 'url_rewriter_enable_images', 1 ), 1 ); ?> /> AI-Generated Images</label><br>
<strong>Image Generation Strategy:</strong><br>
<select name="url_rewriter_image_strategy">
<?php
$url_rewriter_strategy = get_option( 'url_rewriter_image_strategy', 'pexels_ai_fallback' );
$url_rewriter_strategy_options = array(
'pexels_ai_fallback' => 'Pexels Primary + AI Fallback (stock photos + smart fallback)',
'ai_pexels_fallback' => 'AI Primary + Pexels Fallback (full article context + stock fallback)',
'pexels_only' => 'Pexels Only (no AI)',
'disabled' => 'Disabled (no images)'
);
foreach ( $url_rewriter_strategy_options as $value => $label ) {
$selected = selected( $url_rewriter_strategy, $value, false );
echo "<option value='$value' $selected>$label</option>";
}
?>
</select><br><br>
<label><input type="checkbox" name="url_rewriter_enable_seo_validation" value="1" <?php checked( get_option( 'url_rewriter_enable_seo_validation', 1 ), 1 ); ?> /> SEO Validation</label><br>
<label><input type="checkbox" name="url_rewriter_enable_uniqueness" value="1" <?php checked( get_option( 'url_rewriter_enable_uniqueness', 1 ), 1 ); ?> /> Uniqueness Check (prevents duplicates)</label><br><br>
<strong>Slack Notifications:</strong><br>
<label><input type="checkbox" name="url_rewriter_enable_slack_draft" value="1" <?php checked( get_option( 'url_rewriter_enable_slack_draft', 1 ), 1 ); ?> /> Notify for Draft Articles</label><br>
<label><input type="checkbox" name="url_rewriter_enable_slack_published" value="1" <?php checked( get_option( 'url_rewriter_enable_slack_published', 1 ), 1 ); ?> /> Notify for Published Articles</label>
</td>
</tr>
</table>
<?php submit_button( 'Save URL Rewriter Settings' ); ?>
</form>
<hr style="margin: 30px 0;">
<!-- Bulk Import URLs -->
<div style="margin-bottom: 20px;">
<h4>Bulk Import URLs</h4>
<form method="post" action="">
<textarea name="bulk_urls" rows="5" cols="80" placeholder="Enter URLs, one per line..."></textarea><br>
<input type="submit" name="add_bulk_urls" value="Add URLs to Queue" class="button button-primary" />
</form>
</div>
<!-- Import from Sitemap -->
<div style="margin-bottom: 20px;">
<h4>📑 Import from Sitemap</h4>
<form method="post" action="">
<input type="url" name="sitemap_url" size="80" placeholder="https://example.com/sitemap.xml" required>
<input type="submit" name="add_sitemap_urls" value="Import from Sitemap" class="button button-primary" />
<p class="description">Enter the URL of an XML or TXT sitemap. Supports sitemap index files.</p>
</form>
</div>
<?php
// Handle single URL addition
if ( isset( $_POST['add_single_url'] ) && isset( $_POST['single_url'] ) ) {
$url = trim( $_POST['single_url'] );
if ( !empty( $url ) && class_exists( 'Finnish_Global_URL_Rewriter_Module' ) ) {
$url_rewriter = new Finnish_Global_URL_Rewriter_Module();
$result = $url_rewriter->add_url_to_queue( $url, 'normal', 'manual' );
if ( is_wp_error( $result ) ) {
echo '<div class="notice notice-error"><p>Error: ' . esc_html( $result->get_error_message() ) . '</p></div>';
} else {
echo '<div class="notice notice-success"><p>URL added to queue successfully!</p></div>';
}
} else {
echo '<div class="notice notice-error"><p>Invalid URL or module not loaded.</p></div>';
}
}
// Handle bulk URL addition
if ( isset( $_POST['add_bulk_urls'] ) && isset( $_POST['bulk_urls'] ) ) {
$urls_text = $_POST['bulk_urls'];
if ( !empty( $urls_text ) && class_exists( 'Finnish_Global_URL_Rewriter_Module' ) ) {
$url_rewriter = new Finnish_Global_URL_Rewriter_Module();
$result = $url_rewriter->add_bulk_urls_to_queue( $urls_text, 'bulk' );
echo '<div class="notice notice-success"><p>Bulk import completed: ' .
$result['added'] . ' added, ' . $result['skipped'] . ' skipped.</p>';
if ( ! empty( $result['errors'] ) ) {
echo '<p>Errors:</p><ul>';
foreach ( array_slice( $result['errors'], 0, 5 ) as $error ) {
echo '<li>' . esc_html( $error ) . '</li>';
}
echo '</ul>';
}
echo '</div>';
} else {
echo '<div class="notice notice-error"><p>No URLs provided or module not loaded.</p></div>';
}
}
// Handle sitemap import
if ( isset( $_POST['add_sitemap_urls'] ) && isset( $_POST['sitemap_url'] ) ) {
$sitemap_url = trim( $_POST['sitemap_url'] );
if ( !empty( $sitemap_url ) && class_exists( 'Finnish_Global_URL_Rewriter_Module' ) ) {
$url_rewriter = new Finnish_Global_URL_Rewriter_Module();
$result = $url_rewriter->add_sitemap_to_queue( $sitemap_url );
if ( is_wp_error( $result ) ) {
echo '<div class="notice notice-error"><p>Sitemap import failed: ' . esc_html( $result->get_error_message() ) . '</p></div>';
} else {
echo '<div class="notice notice-success"><p>Sitemap import completed: ' .
$result['added'] . ' URLs added, ' . $result['skipped'] . ' skipped.</p>';
if ( ! empty( $result['errors'] ) && count( $result['errors'] ) > 0 ) {
echo '<p>Some URLs could not be added:</p><ul>';
foreach ( array_slice( $result['errors'], 0, 5 ) as $error ) {
echo '<li>' . esc_html( $error ) . '</li>';
}
if ( count( $result['errors'] ) > 5 ) {
echo '<li>... and ' . ( count( $result['errors'] ) - 5 ) . ' more</li>';
}
echo '</ul>';
}
echo '</div>';
}
} else {
echo '<div class="notice notice-error"><p>Invalid sitemap URL or module not loaded.</p></div>';
}
}
?>
<?php
// Display pending URLs (refresh after potential addition)
$pending_urls = get_option( 'url_rewriter_queue', array() );
$processed_urls = get_option( 'url_rewriter_processed', array() );
$failed_urls = get_option( 'url_rewriter_failed', array() );
// Handle bulk URL removal
if ( isset( $_POST['bulk_remove_urls'] ) && isset( $_POST['selected_urls'] ) ) {
$urls_to_remove = $_POST['selected_urls'];
$removed_count = 0;
foreach ( $urls_to_remove as $url_to_remove ) {
if ( isset( $pending_urls[$url_to_remove] ) ) {
unset( $pending_urls[$url_to_remove] );
$removed_count++;
}
}
update_option( 'url_rewriter_queue', $pending_urls );
echo '<div class="notice notice-success"><p>' . $removed_count . ' URLs removed from queue.</p></div>';
// Refresh the pending URLs after removal
$pending_urls = get_option( 'url_rewriter_queue', array() );
}
// Handle single URL removal
if ( isset( $_POST['remove_pending_url'] ) ) {
$url_to_remove = $_POST['remove_pending_url'];
unset( $pending_urls[$url_to_remove] );
update_option( 'url_rewriter_queue', $pending_urls );
echo '<div class="notice notice-success"><p>URL removed from queue.</p></div>';
}
if ( isset( $_POST['remove_processed_url'] ) ) {
$url_to_remove = $_POST['remove_processed_url'];
unset( $processed_urls[$url_to_remove] );
update_option( 'url_rewriter_processed', $processed_urls );
echo '<div class="notice notice-success"><p>Processed URL removed from history.</p></div>';
}
if ( isset( $_POST['clear_all_processed'] ) ) {
update_option( 'url_rewriter_processed', array() );
echo '<div class="notice notice-success"><p>All processed URLs cleared.</p></div>';
$processed_urls = array();
}
// Handle sitemap source deletion
if ( isset( $_POST['delete_sitemap_source'] ) && isset( $_POST['sitemap_url_to_delete'] ) ) {
$sitemap_url = $_POST['sitemap_url_to_delete'];
if ( !empty( $sitemap_url ) && class_exists( 'Finnish_Global_URL_Rewriter_Module' ) ) {
$url_rewriter = new Finnish_Global_URL_Rewriter_Module();
$removed_urls = $url_rewriter->delete_sitemap_source( $sitemap_url );
echo '<div class="notice notice-success"><p>Sitemap source deleted and ' . $removed_urls . ' pending URLs removed.</p></div>';
// Refresh pending URLs after deletion
$pending_urls = get_option( 'url_rewriter_queue', array() );
}
}
?>
<!-- Imported Sitemaps Management -->
<?php
if ( class_exists( 'Finnish_Global_URL_Rewriter_Module' ) ) {
$url_rewriter_for_sitemaps = new Finnish_Global_URL_Rewriter_Module();
// Run data migration on first load to update existing data
static $migration_run = false;
if ( !$migration_run ) {
$migration_result = $url_rewriter_for_sitemaps->migrate_existing_data();
if ( $migration_result['sitemaps_detected'] > 0 ) {
echo '<div class="notice notice-info"><p>Detected and imported ' . $migration_result['sitemaps_detected'] . ' existing sitemap sources from your URLs.</p></div>';
}
$migration_run = true;
}
$imported_sitemaps = $url_rewriter_for_sitemaps->get_imported_sitemaps();
?>
<div style="margin: 20px 0;">
<h4>📑 Sitemap Sources (<?php echo count($imported_sitemaps); ?>)</h4>
<?php if ( !empty( $imported_sitemaps ) ) : ?>
<table class="widefat" style="margin-top: 10px;">
<thead>
<tr>
<th>Sitemap URL</th>
<th>Import Date</th>
<th>URLs Added</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ( $imported_sitemaps as $sitemap_url => $data ) : ?>
<tr>
<td>
<a href="<?php echo esc_url( $sitemap_url ); ?>" target="_blank">
<?php echo esc_html( strlen( $sitemap_url ) > 60 ? substr( $sitemap_url, 0, 60 ) . '...' : $sitemap_url ); ?>
</a>
</td>
<td><?php echo esc_html( isset( $data['import_date'] ) ? $data['import_date'] : 'Unknown' ); ?></td>
<td><?php echo esc_html( isset( $data['urls_added'] ) ? $data['urls_added'] : '0' ); ?></td>
<td>
<form method="post" style="display: inline;">
<input type="hidden" name="sitemap_url_to_delete" value="<?php echo esc_attr( $sitemap_url ); ?>" />
<input type="submit" name="delete_sitemap_source" value="Delete Source" class="button button-small button-link-delete" onclick="return confirm('Delete this sitemap source and remove all pending URLs from this sitemap?')" />
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<p class="description">Deleting a sitemap source will remove all pending URLs that came from that sitemap.</p>
<?php else : ?>
<p>No sitemaps imported yet. Use the "Import from Sitemap" form above to import URLs from sitemaps.</p>
<?php endif; ?>
</div>
<?php } ?>
<div style="margin: 20px 0;">
<?php
// Pagination for Pending URLs
$pending_page = isset( $_GET['pending_page'] ) ? max( 1, intval( $_GET['pending_page'] ) ) : 1;
$pending_per_page = 20;
$pending_total = count( $pending_urls );
$pending_offset = ( $pending_page - 1 ) * $pending_per_page;
$pending_max_pages = ceil( $pending_total / $pending_per_page );
$pending_urls_paginated = array_slice( $pending_urls, $pending_offset, $pending_per_page, true );
?>
<h4>📥 Pending URLs (<?php echo $pending_total; ?>)</h4>
<?php if ( $pending_max_pages > 1 ) : ?>
<div style="margin-bottom: 10px;">
<span>Showing <?php echo min($pending_offset + 1, $pending_total); ?>-<?php echo min($pending_offset + $pending_per_page, $pending_total); ?> of <?php echo $pending_total; ?> URLs</span>
<span style="margin-left: 20px;">
<?php if ( $pending_page > 1 ) : ?>
<a href="?page=finnish-network-global&tab=url_rewriter&pending_page=<?php echo $pending_page - 1; ?><?php echo isset($_GET['processed_page']) ? '&processed_page=' . $_GET['processed_page'] : ''; ?>" class="button button-secondary">« Previous</a>
<?php endif; ?>
<span style="margin: 0 10px;">Page <?php echo $pending_page; ?> of <?php echo $pending_max_pages; ?></span>
<?php if ( $pending_page < $pending_max_pages ) : ?>
<a href="?page=finnish-network-global&tab=url_rewriter&pending_page=<?php echo $pending_page + 1; ?><?php echo isset($_GET['processed_page']) ? '&processed_page=' . $_GET['processed_page'] : ''; ?>" class="button button-secondary">Next »</a>
<?php endif; ?>
</span>
</div>
<?php endif; ?>
<?php if ( !empty( $pending_urls_paginated ) ) : ?>
<form id="bulk_url_form" method="post">
<div style="margin-bottom: 10px;">
<input type="button" id="select_all_urls" value="Select All" class="button button-secondary" onclick="toggleSelectAll()" />
<input type="button" id="deselect_all_urls" value="Deselect All" class="button button-secondary" onclick="deselectAll()" style="display:none;" />
<input type="submit" name="bulk_remove_urls" value="Remove Selected" class="button button-secondary" onclick="return confirm('Remove selected URLs from queue?')" disabled id="bulk_remove_btn" />
</div>
<table class="widefat" style="margin-top: 10px;">
<thead>
<tr>
<th style="width: 30px;"><input type="checkbox" id="select_all_checkbox" onchange="toggleSelectAll()" /></th>
<th>URL</th>
<th>Source</th>
<th>Added</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ( $pending_urls_paginated as $url => $data ) : ?>
<?php
// Handle different data formats
$source = 'manual'; // default
$added_date = 'Unknown';
$source_sitemap = '';
if ( is_array( $data ) ) {
$source = isset( $data['source'] ) ? $data['source'] : 'manual';
$added_date = isset( $data['added_date'] ) ? $data['added_date'] : 'Unknown';
$source_sitemap = isset( $data['source_sitemap'] ) ? $data['source_sitemap'] : '';
} else {
// Old format - just a string or simple data
$added_date = is_string( $data ) ? $data : 'Unknown';
}
$source_display = ucfirst( $source );
if ( $source === 'sitemap' && ! empty( $source_sitemap ) ) {
$source_display = '<span title="' . esc_attr( $source_sitemap ) . '">Sitemap</span>';
}
?>
<tr>
<td><input type="checkbox" name="selected_urls[]" value="<?php echo esc_attr( $url ); ?>" class="url_checkbox" onchange="updateBulkButton()" /></td>
<td><a href="<?php echo esc_url( $url ); ?>" target="_blank"><?php echo esc_html( $url ); ?></a></td>
<td><?php echo $source_display; ?></td>
<td><?php echo esc_html( $added_date ); ?></td>
<td>
<form method="post" style="display: inline;">
<input type="hidden" name="remove_pending_url" value="<?php echo esc_attr( $url ); ?>" />
<input type="submit" value="Remove" class="button button-small" onclick="return confirm('Remove this URL from queue?')" />
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</form>
<script>
function toggleSelectAll() {
var selectAllCheckbox = document.getElementById('select_all_checkbox');
var urlCheckboxes = document.querySelectorAll('.url_checkbox');
var selectAllBtn = document.getElementById('select_all_urls');
var deselectAllBtn = document.getElementById('deselect_all_urls');
urlCheckboxes.forEach(function(checkbox) {
checkbox.checked = selectAllCheckbox.checked;
});
if (selectAllCheckbox.checked) {
selectAllBtn.style.display = 'none';
deselectAllBtn.style.display = 'inline-block';
} else {
selectAllBtn.style.display = 'inline-block';
deselectAllBtn.style.display = 'none';
}
updateBulkButton();
}
function deselectAll() {
var selectAllCheckbox = document.getElementById('select_all_checkbox');
var urlCheckboxes = document.querySelectorAll('.url_checkbox');
var selectAllBtn = document.getElementById('select_all_urls');
var deselectAllBtn = document.getElementById('deselect_all_urls');
selectAllCheckbox.checked = false;
urlCheckboxes.forEach(function(checkbox) {
checkbox.checked = false;
});
selectAllBtn.style.display = 'inline-block';
deselectAllBtn.style.display = 'none';
updateBulkButton();
}
function updateBulkButton() {
var checkedBoxes = document.querySelectorAll('.url_checkbox:checked');
var bulkRemoveBtn = document.getElementById('bulk_remove_btn');
bulkRemoveBtn.disabled = checkedBoxes.length === 0;
// Update select all checkbox state
var allCheckboxes = document.querySelectorAll('.url_checkbox');
var selectAllCheckbox = document.getElementById('select_all_checkbox');
var selectAllBtn = document.getElementById('select_all_urls');
var deselectAllBtn = document.getElementById('deselect_all_urls');
if (checkedBoxes.length === allCheckboxes.length && allCheckboxes.length > 0) {
selectAllCheckbox.checked = true;
selectAllBtn.style.display = 'none';
deselectAllBtn.style.display = 'inline-block';
} else if (checkedBoxes.length === 0) {
selectAllCheckbox.checked = false;
selectAllBtn.style.display = 'inline-block';
deselectAllBtn.style.display = 'none';
} else {
selectAllCheckbox.checked = false;
selectAllBtn.style.display = 'inline-block';
deselectAllBtn.style.display = 'none';
}
}
</script>
<?php else : ?>
<p>No URLs in queue. Add URLs above to get started.</p>
<?php endif; ?>
</div>
<div style="margin: 20px 0;">
<?php
// Pagination for Processed URLs
$processed_page = isset( $_GET['processed_page'] ) ? max( 1, intval( $_GET['processed_page'] ) ) : 1;
$processed_per_page = 20;
$sorted_processed = array_reverse( $processed_urls, true ); // Show most recent first
$processed_total = count( $sorted_processed );
$processed_offset = ( $processed_page - 1 ) * $processed_per_page;
$processed_max_pages = ceil( $processed_total / $processed_per_page );
$processed_urls_paginated = array_slice( $sorted_processed, $processed_offset, $processed_per_page, true );
?>
<h4>✅ Processed URLs (<?php echo $processed_total; ?>)</h4>
<?php if ( !empty( $processed_urls ) ) : ?>
<form method="post" style="margin-bottom: 10px;">
<input type="hidden" name="clear_all_processed" value="1" />
<input type="submit" value="Clear All Processed" class="button button-secondary" onclick="return confirm('Clear all processed URLs from history?')" />
</form>
<?php if ( $processed_max_pages > 1 ) : ?>
<div style="margin-bottom: 10px;">
<span>Showing <?php echo min($processed_offset + 1, $processed_total); ?>-<?php echo min($processed_offset + $processed_per_page, $processed_total); ?> of <?php echo $processed_total; ?> URLs</span>
<span style="margin-left: 20px;">
<?php if ( $processed_page > 1 ) : ?>
<a href="?page=finnish-network-global&tab=url_rewriter&processed_page=<?php echo $processed_page - 1; ?><?php echo isset($_GET['pending_page']) ? '&pending_page=' . $_GET['pending_page'] : ''; ?>" class="button button-secondary">« Previous</a>
<?php endif; ?>
<span style="margin: 0 10px;">Page <?php echo $processed_page; ?> of <?php echo $processed_max_pages; ?></span>
<?php if ( $processed_page < $processed_max_pages ) : ?>
<a href="?page=finnish-network-global&tab=url_rewriter&processed_page=<?php echo $processed_page + 1; ?><?php echo isset($_GET['pending_page']) ? '&pending_page=' . $_GET['pending_page'] : ''; ?>" class="button button-secondary">Next »</a>
<?php endif; ?>
</span>
</div>
<?php endif; ?>
<table class="widefat">
<thead>
<tr>
<th>URL</th>
<th>Source</th>
<th>Processed Date</th>
<th>Post Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ( $processed_urls_paginated as $url => $data ) : ?>
<?php
// Get source information for processed URLs
$processed_source = 'manual'; // default
$processed_source_sitemap = '';
if ( is_array( $data ) ) {
$processed_source = isset( $data['source'] ) ? $data['source'] : 'manual';
$processed_source_sitemap = isset( $data['source_sitemap'] ) ? $data['source_sitemap'] : '';
}
$processed_source_display = ucfirst( $processed_source );
if ( $processed_source === 'sitemap' && ! empty( $processed_source_sitemap ) ) {
$processed_source_display = '<span title="' . esc_attr( $processed_source_sitemap ) . '">Sitemap</span>';
}
?>
<tr>
<td><a href="<?php echo esc_url( $url ); ?>" target="_blank"><?php echo esc_html( $url ); ?></a></td>
<td><?php echo $processed_source_display; ?></td>
<td><?php echo esc_html( isset( $data['processed_date'] ) ? $data['processed_date'] : 'Unknown' ); ?></td>
<td>
<?php if ( isset( $data['post_id'] ) && get_post( $data['post_id'] ) ) : ?>
<a href="<?php echo get_edit_post_link( $data['post_id'] ); ?>">Edit Post #<?php echo $data['post_id']; ?></a>
<?php else : ?>
<?php echo esc_html( isset( $data['message'] ) ? $data['message'] : 'No post data' ); ?>
<?php endif; ?>
</td>
<td>
<form method="post" style="display: inline;">
<input type="hidden" name="remove_processed_url" value="<?php echo esc_attr( $url ); ?>" />
<input type="submit" value="Remove" class="button button-small" onclick="return confirm('Remove this URL from history?')" />
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<p class="description">Showing 20 most recent processed URLs.</p>
<?php else : ?>
<p>No URLs processed yet.</p>
<?php endif; ?>
</div>
<?php if ( !empty( $failed_urls ) ) : ?>
<div style="margin: 20px 0;">
<h4>❌ Failed URLs (<?php echo count($failed_urls); ?>)</h4>
<table class="widefat">
<thead>
<tr>
<th>URL</th>
<th>Failed Date</th>
<th>Error</th>
</tr>
</thead>
<tbody>
<?php foreach ( $failed_urls as $url => $data ) : ?>
<tr>
<td><a href="<?php echo esc_url( $url ); ?>" target="_blank"><?php echo esc_html( $url ); ?></a></td>
<td><?php echo esc_html( isset( $data['failed_date'] ) ? $data['failed_date'] : 'Unknown' ); ?></td>
<td><?php echo esc_html( isset( $data['error'] ) ? $data['error'] : 'Unknown error' ); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<p><strong>Module Status:</strong> <?php echo class_exists( 'Finnish_Global_URL_Rewriter_Module' ) ? 'Loaded Successfully' : 'Not Loaded'; ?></p>
</div>
<?php elseif ( $active_tab == 'category_scraper' ) : ?>
<!-- CATEGORY SCRAPER TAB -->
<div class="finnish-network-card">
<h2>📰 Category Scraper Module</h2>
<p>Auto-discover articles from website categories and rewrite them with AI enhancement.</p>
<h3>🔧 Settings</h3>
<form method="post" action="options.php">
<?php settings_fields( 'finnish_global_category_scraper_options' ); ?>
<table class="form-table">
<tr>
<th scope="row">Enhancement Features</th>
<td>
<label><input type="checkbox" name="category_scraper_enable_perplexity" value="1" <?php checked( get_option( 'category_scraper_enable_perplexity' ), 1 ); ?> /> Perplexity Fact-Checking</label><br>
<label><input type="checkbox" name="category_scraper_enable_lsi" value="1" <?php checked( get_option( 'category_scraper_enable_lsi' ), 1 ); ?> /> LSI Keywords</label><br>
<label><input type="checkbox" name="category_scraper_enable_internal_linking" value="1" <?php checked( get_option( 'category_scraper_enable_internal_linking' ), 1 ); ?> /> Internal Linking</label><br>
<label><input type="checkbox" name="category_scraper_enable_images" value="1" <?php checked( get_option( 'category_scraper_enable_images' ), 1 ); ?> /> AI-Generated Images</label><br>
<strong>Image Generation Strategy:</strong><br>
<select name="category_scraper_image_strategy">
<?php
$category_scraper_strategy = get_option( 'category_scraper_image_strategy', 'pexels_ai_fallback' );
$category_scraper_strategy_options = array(
'pexels_ai_fallback' => 'Pexels Primary + AI Fallback (stock photos + smart fallback)',
'ai_pexels_fallback' => 'AI Primary + Pexels Fallback (full article context + stock fallback)',
'pexels_only' => 'Pexels Only (no AI)',
'disabled' => 'Disabled (no images)'
);
foreach ( $category_scraper_strategy_options as $value => $label ) {
$selected = selected( $category_scraper_strategy, $value, false );
echo "<option value='$value' $selected>$label</option>";
}
?>
</select><br><br>
<label><input type="checkbox" name="category_scraper_enable_seo_validation" value="1" <?php checked( get_option( 'category_scraper_enable_seo_validation' ), 1 ); ?> /> SEO Validation</label><br>
<label><input type="checkbox" name="category_scraper_enable_uniqueness" value="1" <?php checked( get_option( 'category_scraper_enable_uniqueness' ), 1 ); ?> /> Uniqueness Check (prevents duplicates)</label><br><br>
<strong>Slack Notifications:</strong><br>
<label><input type="checkbox" name="category_scraper_enable_slack_draft" value="1" <?php checked( get_option( 'category_scraper_enable_slack_draft' ), 1 ); ?> /> Notify for Draft Articles</label><br>
<label><input type="checkbox" name="category_scraper_enable_slack_published" value="1" <?php checked( get_option( 'category_scraper_enable_slack_published' ), 1 ); ?> /> Notify for Published Articles</label>
</td>
</tr>
</table>
<?php submit_button( 'Save Category Scraper Settings' ); ?>
</form>
<h3>🌐 Source Management</h3>
<form method="post" action="">
<table class="form-table">
<tr>
<th scope="row">Source Name</th>
<td>
<input type="text" name="source_name" class="regular-text" required placeholder="e.g., Casino News Category">
<p class="description">Descriptive name for this source</p>
</td>
</tr>
<tr>
<th scope="row">Complete Category URL</th>
<td>
<input type="url" name="category_url" class="regular-text" required placeholder="https://example.com/category/news">
<p class="description"><strong>Enter the COMPLETE URL</strong> of the category page that lists articles.<br>
<strong>Example:</strong> https://example.com/category/tech<br>
<strong>NOT:</strong> Base URL + separate path - combine them into one complete URL.</p>
</td>
</tr>
<tr>
<th scope="row">Check Frequency</th>
<td>
<select name="check_frequency">
<option value="daily">Daily</option>
<option value="hourly">Hourly</option>
<option value="weekly">Weekly</option>
</select>
</td>
</tr>
</table>
<input type="submit" name="add_source" value="Add Source" class="button button-primary" />
</form>
<?php
// Handle source addition
if ( isset( $_POST['add_source'] ) && isset( $_POST['category_url'] ) && isset( $_POST['source_name'] ) && class_exists( 'Finnish_Global_Category_Scraper_Module' ) ) {
$category_scraper = new Finnish_Global_Category_Scraper_Module();
$category_url = sanitize_url( $_POST['category_url'] );
$source_name = sanitize_text_field( $_POST['source_name'] );
$result = $category_scraper->add_category_source( $category_url, $source_name, 'normal' );
if ( is_wp_error( $result ) ) {
echo '<div class="notice notice-error"><p>Failed to add source: ' . esc_html( $result->get_error_message() ) . '</p></div>';
} else {
echo '<div class="notice notice-success"><p>Source "' . esc_html( $source_name ) . '" added successfully!</p></div>';
}
}
?>
<h3>🌐 Configured Sources</h3>
<?php
if ( class_exists( 'Finnish_Global_Category_Scraper_Module' ) ) {
$category_scraper = new Finnish_Global_Category_Scraper_Module();
// Trigger data migration for existing category sources
static $category_migration_run = false;
if ( !$category_migration_run ) {
$category_migration_result = $category_scraper->migrate_existing_data();
if ( $category_migration_result['sources_migrated'] > 0 ) {
echo '<div class="notice notice-info"><p>Migrated ' . $category_migration_result['sources_migrated'] . ' existing category sources to new display format.</p></div>';
}
$category_migration_run = true;
}
$sources = $category_scraper->get_category_sources();
if ( !empty( $sources ) ) {
echo '<table class="wp-list-table widefat fixed striped" style="margin-top: 10px;">';
echo '<thead><tr><th>Source Name</th><th>Category URL</th><th>Added Date</th><th>Actions</th></tr></thead><tbody>';
foreach ( $sources as $source ) {
echo '<tr>';
echo '<td><strong>' . esc_html( $source['name'] ) . '</strong></td>';
echo '<td><a href="' . esc_url( $source['url'] ) . '" target="_blank">' . esc_html( $source['url'] ) . '</a></td>';
echo '<td>' . esc_html( isset( $source['added_date'] ) ? $source['added_date'] : 'Unknown' ) . '</td>';
echo '<td>';
echo '<form method="post" style="display: inline;">';
echo '<input type="hidden" name="remove_source_url" value="' . esc_attr( $source['url'] ) . '">';
echo '<input type="submit" value="Remove" class="button button-small button-link-delete" onclick="return confirm(\'Are you sure you want to remove this source?\')">';
echo '</form>';
echo '</td>';
echo '</tr>';
}
echo '</tbody></table>';
} else {
echo '<p>No sources configured yet. Add your first source above.</p>';
}
// Handle source removal - inline processing
if ( isset( $_POST['remove_source_url'] ) ) {
$url_to_remove = sanitize_url( $_POST['remove_source_url'] );
$result = $category_scraper->remove_category_source( $url_to_remove );
if ( $result ) {
echo '<div class="notice notice-success"><p>Source removed successfully!</p></div>';
echo '<meta http-equiv="refresh" content="0">';
} else {
echo '<div class="notice notice-error"><p>Failed to remove source.</p></div>';
}
}
}
?>
<h3>📥 Discovered Articles (Pending)</h3>
<?php
if ( class_exists( 'Finnish_Global_Category_Scraper_Module' ) ) {
$category_scraper = new Finnish_Global_Category_Scraper_Module();
$pending_articles = $category_scraper->get_pending_articles();
if ( !empty( $pending_articles ) ) {
echo '<table class="wp-list-table widefat fixed striped" style="margin-top: 10px;">';
echo '<thead><tr><th>Article Title & URL</th><th>Source</th><th>Discovered</th><th>Actions</th></tr></thead><tbody>';
$displayed = 0;
foreach ( $pending_articles as $article ) {
if ( $displayed >= 20 ) break; // Limit display
echo '<tr>';
echo '<td>';
if ( !empty( $article['title'] ) ) {
echo '<strong>' . esc_html( $article['title'] ) . '</strong><br>';
}
echo '<a href="' . esc_url( $article['url'] ) . '" target="_blank" style="font-size: 0.9em; color: #666;">' . esc_html( $article['url'] ) . '</a>';
echo '</td>';
echo '<td>' . esc_html( $article['source_name'] ) . '</td>';
echo '<td>' . esc_html( $article['discovered_date'] ) . '</td>';
echo '<td>';
echo '<form method="post" style="display: inline;">';
echo '<input type="hidden" name="remove_pending_article" value="' . esc_attr( $article['url'] ) . '">';
echo '<input type="submit" value="Remove" class="button button-small button-link-delete" onclick="return confirm(\'Remove this article from the queue?\')">';
echo '</form>';
echo '</td>';
echo '</tr>';
$displayed++;
}
if ( count( $pending_articles ) > 20 ) {
echo '<tr><td colspan="4"><em>... and ' . (count( $pending_articles ) - 20) . ' more articles waiting to be processed</em></td></tr>';
}
echo '</tbody></table>';
echo '<p class="description">These articles were discovered from your configured sources and will be processed by the Category Scraper scheduler.</p>';
// DEBUG: Show raw queue data
if ( isset( $_GET['debug_queue'] ) ) {
$raw_queue = get_option( 'category_scraper_queue', array() );
echo '<div style="background: #f0f0f0; padding: 10px; margin: 10px 0; font-family: monospace; font-size: 12px;">';
echo '<strong>DEBUG - Raw Queue Data:</strong><br>';
echo '<pre>' . print_r( $raw_queue, true ) . '</pre>';
echo '</div>';
} else {
echo '<p><a href="?page=finnish-network-global&tab=category_scraper&debug_queue=1">🐛 Debug Queue Data</a></p>';
}
// Add cleanup button
echo '<form method="post" style="display: inline; margin: 10px 0;">';
echo '<input type="submit" name="cleanup_queue" value="🧹 Cleanup Old Queue Data" class="button button-secondary" onclick="return confirm(\'This will remove old malformed queue entries. Continue?\')">';
echo '</form>';
} else {
echo '<p>No pending articles found. Make sure you have configured category sources above.</p>';
}
// Handle queue cleanup
if ( isset( $_POST['cleanup_queue'] ) && class_exists( 'Finnish_Global_Category_Scraper_Module' ) ) {
$category_scraper = new Finnish_Global_Category_Scraper_Module();
$result = $category_scraper->cleanup_malformed_queue();
if ( $result ) {
echo '<div class="notice notice-success"><p>Queue cleanup completed! Malformed entries removed.</p></div>';
echo '<meta http-equiv="refresh" content="0">';
} else {
echo '<div class="notice notice-info"><p>No cleanup needed - queue data is already properly formatted.</p></div>';
}
}
// Handle article removal from queue - inline processing
if ( isset( $_POST['remove_pending_article'] ) && class_exists( 'Finnish_Global_Category_Scraper_Module' ) ) {
$category_scraper = new Finnish_Global_Category_Scraper_Module();
$article_url_to_remove = sanitize_url( $_POST['remove_pending_article'] );
$result = $category_scraper->remove_from_queue( $article_url_to_remove );
if ( $result ) {
echo '<div class="notice notice-success"><p>Article removed from queue!</p></div>';
echo '<meta http-equiv="refresh" content="0">';
} else {
echo '<div class="notice notice-error"><p>Failed to remove article. Try cleanup first if needed.</p></div>';
}
}
}
?>
<h3>📊 Processing Statistics</h3>
<?php
$category_sources = get_option( 'category_scraper_sources', array() );
$processed_articles = get_option( 'category_scraper_processed', array() );
?>
<div style="margin: 20px 0;">
<h4>📈 Recent Activity</h4>
<?php if ( !empty( $processed_articles ) ) : ?>
<table class="widefat">
<thead>
<tr>
<th>Article URL</th>
<th>Source</th>
<th>Processed Date</th>
<th>Post Created</th>
</tr>
</thead>
<tbody>
<?php
// Show most recent first
$sorted_articles = array_reverse( $processed_articles, true );
$count = 0;
foreach ( $sorted_articles as $url => $data ) :
if ( ++$count > 15 ) break; // Limit to 15 most recent
?>
<tr>
<td><a href="<?php echo esc_url( $url ); ?>" target="_blank"><?php echo esc_html( $url ); ?></a></td>
<td><?php echo esc_html( isset( $data['source_name'] ) ? $data['source_name'] : 'Unknown' ); ?></td>
<td><?php echo esc_html( isset( $data['processed_date'] ) ? $data['processed_date'] : 'Unknown' ); ?></td>
<td>
<?php if ( isset( $data['post_id'] ) && get_post( $data['post_id'] ) ) : ?>
<a href="<?php echo get_edit_post_link( $data['post_id'] ); ?>">Edit Post #<?php echo $data['post_id']; ?></a>
<?php else : ?>
Post not found
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<p class="description">Showing 15 most recent processed articles.</p>
<?php else : ?>
<p>No articles processed yet. Add sources above and wait for the scheduler to run.</p>
<?php endif; ?>
</div>
<p><strong>Module Status:</strong> <?php echo class_exists( 'Finnish_Global_Category_Scraper_Module' ) ? 'Loaded Successfully' : 'Not Loaded'; ?></p>
</div>
<?php elseif ( $active_tab == 'internal_linking' ) : ?>
<!-- INTERNAL LINKING INTELLIGENCE TAB -->
<?php
// Get module instance
$linking_module = isset($GLOBALS['finnish_global_internal_linking']) ?
$GLOBALS['finnish_global_internal_linking'] : null;
// Handle form submissions
if (isset($_POST['internal_linking_action'])) {
if ($_POST['internal_linking_action'] == 'save_settings' && wp_verify_nonce($_POST['internal_linking_nonce'], 'internal_linking_settings')) {
// Save settings
if ($linking_module) {
// Basic settings
$linking_module->update_setting('enabled', isset($_POST['enabled']) ? '1' : '0');
// Scheduler settings
if (isset($_POST['scheduler_frequency'])) {
$linking_module->update_setting('scheduler_frequency', sanitize_text_field($_POST['scheduler_frequency']));
}
if (isset($_POST['scheduler_time'])) {
$linking_module->update_setting('scheduler_time', sanitize_text_field($_POST['scheduler_time']));
}
if (isset($_POST['process_articles_per_run'])) {
$linking_module->update_setting('process_articles_per_run', intval($_POST['process_articles_per_run']));
}
if (isset($_POST['old_article_days_limit'])) {
$linking_module->update_setting('old_article_days_limit', intval($_POST['old_article_days_limit']));
}
// Link limits
if (isset($_POST['max_links_per_article'])) {
$linking_module->update_setting('max_links_per_article', intval($_POST['max_links_per_article']));
}
if (isset($_POST['max_new_links_per_session'])) {
$linking_module->update_setting('max_new_links_per_session', intval($_POST['max_new_links_per_session']));
}
if (isset($_POST['min_article_word_count'])) {
$linking_module->update_setting('min_article_word_count', intval($_POST['min_article_word_count']));
}
// Quality thresholds
if (isset($_POST['min_relevance_score'])) {
$linking_module->update_setting('min_relevance_score', intval($_POST['min_relevance_score']));
}
if (isset($_POST['auto_approve_threshold'])) {
$linking_module->update_setting('auto_approve_threshold', intval($_POST['auto_approve_threshold']));
}
if (isset($_POST['automation_mode'])) {
$linking_module->update_setting('automation_mode', sanitize_text_field($_POST['automation_mode']));
}
// Log the settings save
Finnish_Global_Logger::log('Internal Linking Intelligence: Settings updated by user ' . get_current_user_id());
echo '<div class="notice notice-success"><p>All Internal Linking settings saved successfully!</p></div>';
}
} elseif ($_POST['internal_linking_action'] == 'run_now' && wp_verify_nonce($_POST['internal_linking_nonce'], 'internal_linking_run')) {
// Run manual processing
if ($linking_module) {
Finnish_Global_Logger::log('Internal Linking Intelligence: Manual processing started by user ' . get_current_user_id());
$result = $linking_module->process_internal_linking(true);
if ($result['status'] == 'success') {
Finnish_Global_Logger::log('Internal Linking Intelligence: Manual processing completed - ' . $result['articles_processed'] . ' articles processed, ' . $result['links_created'] . ' links created');
echo '<div class="notice notice-success"><p>Processing complete! Processed ' . $result['articles_processed'] . ' articles, created ' . $result['links_created'] . ' links, queued ' . $result['links_queued'] . ' for approval.</p></div>';
} else {
Finnish_Global_Logger::log('Internal Linking Intelligence: Manual processing failed - ' . $result['message']);
echo '<div class="notice notice-warning"><p>' . $result['message'] . '</p></div>';
}
}
} elseif ($_POST['internal_linking_action'] == 'approve_link' && wp_verify_nonce($_POST['internal_linking_nonce'], 'internal_linking_approve')) {
// Approve a queued link
if ($linking_module && $linking_module->approve_link(intval($_POST['queue_id']))) {
echo '<div class="notice notice-success"><p>Link approved and inserted!</p></div>';
}
} elseif ($_POST['internal_linking_action'] == 'reject_link' && wp_verify_nonce($_POST['internal_linking_nonce'], 'internal_linking_reject')) {
// Reject a queued link
if ($linking_module && $linking_module->reject_link(intval($_POST['queue_id']))) {
echo '<div class="notice notice-success"><p>Link rejected.</p></div>';
}
} elseif ($_POST['internal_linking_action'] == 'remove_link' && wp_verify_nonce($_POST['internal_linking_nonce'], 'internal_linking_remove')) {
// Remove an existing link
if ($linking_module && $linking_module->remove_link(intval($_POST['history_id']))) {
echo '<div class="notice notice-success"><p>Link removed successfully!</p></div>';
}
}
}
// Get statistics if module is loaded
$stats = $linking_module ? $linking_module->get_statistics() : array();
?>
<div class="finnish-network-card">
<h2>🔗 Internal Linking Intelligence</h2>
<p>Automatically creates contextual internal links from old articles to new ones using existing phrases as anchor text for natural SEO boost.</p>
<!-- Statistics Dashboard -->
<div style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 20px; margin: 30px 0;">
<div style="background: #fff; padding: 20px; border-radius: 8px; text-align: center; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<div style="font-size: 32px; font-weight: bold; color: #2271b1;">
<?php echo isset($stats['total_links']) ? $stats['total_links'] : 0; ?>
</div>
<div style="color: #666; margin-top: 5px;">Total Links Created</div>
</div>
<div style="background: #fff; padding: 20px; border-radius: 8px; text-align: center; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<div style="font-size: 32px; font-weight: bold; color: #f0b849;">
<?php echo isset($stats['pending_links']) ? $stats['pending_links'] : 0; ?>
</div>
<div style="color: #666; margin-top: 5px;">Pending Approval</div>
</div>
<div style="background: #fff; padding: 20px; border-radius: 8px; text-align: center; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<div style="font-size: 32px; font-weight: bold; color: #00a32a;">
<?php echo isset($stats['links_today']) ? $stats['links_today'] : 0; ?>
</div>
<div style="color: #666; margin-top: 5px;">Links Today</div>
</div>
<div style="background: #fff; padding: 20px; border-radius: 8px; text-align: center; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<div style="font-size: 32px; font-weight: bold; color: #8c5fc2;">
<?php echo isset($stats['links_week']) ? $stats['links_week'] : 0; ?>
</div>
<div style="color: #666; margin-top: 5px;">Links This Week</div>
</div>
<div style="background: #fff; padding: 20px; border-radius: 8px; text-align: center; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<div style="font-size: 32px; font-weight: bold; color: #135e96;">
<?php echo isset($stats['avg_relevance']) ? $stats['avg_relevance'] : 0; ?>%
</div>
<div style="color: #666; margin-top: 5px;">Avg Relevance</div>
</div>
</div>
<!-- Settings Form -->
<h3>⚙️ Configuration</h3>
<?php if ($linking_module) : ?>
<form method="post" action="">
<input type="hidden" name="internal_linking_action" value="save_settings">
<?php wp_nonce_field('internal_linking_settings', 'internal_linking_nonce'); ?>
<table class="form-table">
<tr>
<th scope="row">Module Status</th>
<td>
<label>
<input type="checkbox" name="enabled" value="1" <?php checked($linking_module->get_setting('enabled', '1'), '1'); ?>>
Enable Internal Linking Intelligence
</label>
<p class="description">When enabled, the module will automatically process new articles and create internal links.</p>
</td>
</tr>
<tr>
<th scope="row">Automation Mode</th>
<td>
<?php $mode = $linking_module->get_setting('automation_mode', 'preview'); ?>
<select name="automation_mode">
<option value="auto" <?php selected($mode, 'auto'); ?>>Full Auto (Insert links immediately)</option>
<option value="preview" <?php selected($mode, 'preview'); ?>>Preview Mode (Queue for approval)</option>
<option value="hybrid" <?php selected($mode, 'hybrid'); ?>>Hybrid (Auto-approve high confidence)</option>
</select>
<p class="description">Choose how links are processed - automatically inserted or queued for review.</p>
</td>
</tr>
<tr>
<th scope="row">Link Limits</th>
<td>
<p>
<label>Max total internal links per article:
<input type="number" name="max_links_per_article" min="1" max="20" value="<?php echo $linking_module->get_setting('max_links_per_article', '5'); ?>" class="small-text">
</label>
</p>
<p>
<label>Max new links to add per session:
<input type="number" name="max_new_links_per_session" min="1" max="10" value="<?php echo $linking_module->get_setting('max_new_links_per_session', '3'); ?>" class="small-text">
</label>
</p>
<p class="description">Prevents over-optimization by limiting link density.</p>
</td>
</tr>
<tr>
<th scope="row">Quality Thresholds</th>
<td>
<p>
<label>Minimum relevance score (0-100):
<input type="number" name="min_relevance_score" min="0" max="100" value="<?php echo $linking_module->get_setting('min_relevance_score', '75'); ?>" class="small-text">
</label>
</p>
<p>
<label>Auto-approve threshold (hybrid mode):
<input type="number" name="auto_approve_threshold" min="0" max="100" value="<?php echo $linking_module->get_setting('auto_approve_threshold', '90'); ?>" class="small-text">
</label>
</p>
<p class="description">Higher scores mean more selective linking for better quality.</p>
</td>
</tr>
<tr>
<th scope="row">Processing Limits</th>
<td>
<p>
<label>Process articles per run:
<input type="number" name="process_articles_per_run" min="1" max="50" value="<?php echo $linking_module->get_setting('process_articles_per_run', '5'); ?>" class="small-text">
</label>
</p>
<p>
<label>Look back days for old articles:
<input type="number" name="old_article_days_limit" min="30" max="365" value="<?php echo $linking_module->get_setting('old_article_days_limit', '180'); ?>" class="small-text">
</label>
</p>
<p class="description">Control processing load and article selection range.</p>
</td>
</tr>
<tr>
<th scope="row">Scheduler Settings</th>
<td>
<p>
<label>Frequency:
<?php $freq = $linking_module->get_setting('scheduler_frequency', 'weekly'); ?>
<select name="scheduler_frequency">
<option value="daily" <?php selected($freq, 'daily'); ?>>Daily</option>
<option value="weekly" <?php selected($freq, 'weekly'); ?>>Weekly</option>
<option value="monthly" <?php selected($freq, 'monthly'); ?>>Monthly</option>
</select>
</label>
</p>
<p>
<label>Run time:
<input type="time" name="scheduler_time" value="<?php echo $linking_module->get_setting('scheduler_time', '03:00'); ?>">
</label>
</p>
<p class="description">When the automatic processing should run.</p>
</td>
</tr>
</table>
<?php submit_button('Save Settings'); ?>
</form>
<!-- Manual Run Button -->
<div style="margin-top: 20px; padding: 20px; background: #f6f7f7; border-radius: 8px;">
<h4>🚀 Manual Processing</h4>
<form method="post" action="" style="display: inline;">
<input type="hidden" name="internal_linking_action" value="run_now">
<?php wp_nonce_field('internal_linking_run', 'internal_linking_nonce'); ?>
<button type="submit" class="button button-primary">Run Now</button>
</form>
<span style="margin-left: 10px; color: #666;">Process new articles and create internal links immediately.</span>
<?php
$last_run = $linking_module->get_setting('last_run_date');
if ($last_run) {
echo '<p style="margin-top: 10px; color: #666;">Last run: ' . human_time_diff(strtotime($last_run)) . ' ago (' . $last_run . ')</p>';
}
?>
</div>
<?php
// Pending Approval Queue
$pending_links = $linking_module->get_pending_links(20);
if (!empty($pending_links)) :
?>
<h3>✅ Pending Approval Queue (<?php echo count($pending_links); ?>)</h3>
<div style="background: #fff; padding: 20px; border-radius: 8px; border: 1px solid #c3c4c7;">
<table class="widefat">
<thead>
<tr>
<th>Source Article</th>
<th>Target Article</th>
<th>Anchor Text</th>
<th>Relevance</th>
<th>Context</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($pending_links as $link) : ?>
<tr>
<td>
<a href="<?php echo get_edit_post_link($link->source_post_id); ?>" target="_blank">
<?php echo esc_html($link->source_title); ?>
</a>
</td>
<td>
<a href="<?php echo get_permalink($link->target_post_id); ?>" target="_blank">
<?php echo esc_html($link->target_title); ?>
</a>
</td>
<td><strong><?php echo esc_html($link->anchor_text); ?></strong></td>
<td>
<span style="color: <?php echo $link->relevance_score >= 90 ? '#00a32a' : ($link->relevance_score >= 75 ? '#f0b849' : '#d63638'); ?>;">
<?php echo round($link->relevance_score); ?>%
</span>
</td>
<td>
<small style="color: #666;">
...<?php echo esc_html($link->context_before); ?>
<strong style="background: #fffbcc;"><?php echo esc_html($link->anchor_text); ?></strong>
<?php echo esc_html($link->context_after); ?>...
</small>
</td>
<td>
<form method="post" style="display: inline;">
<input type="hidden" name="internal_linking_action" value="approve_link">
<input type="hidden" name="queue_id" value="<?php echo $link->id; ?>">
<?php wp_nonce_field('internal_linking_approve', 'internal_linking_nonce'); ?>
<button type="submit" class="button button-primary button-small">Approve</button>
</form>
<form method="post" style="display: inline;">
<input type="hidden" name="internal_linking_action" value="reject_link">
<input type="hidden" name="queue_id" value="<?php echo $link->id; ?>">
<?php wp_nonce_field('internal_linking_reject', 'internal_linking_nonce'); ?>
<button type="submit" class="button button-secondary button-small">Reject</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<!-- Scheduler Overview Section -->
<h3>📅 Scheduler Status & Controls</h3>
<div style="background: #f8f9fa; padding: 20px; border-radius: 8px; border-left: 4px solid #2271b1; margin-bottom: 30px;">
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr auto; gap: 20px; align-items: center;">
<div>
<strong>Current Schedule:</strong><br>
<span style="color: #666;">
<?php
$freq = $linking_module->get_setting('scheduler_frequency', 'weekly');
$time = $linking_module->get_setting('scheduler_time', '03:00');
echo ucfirst($freq) . ' at ' . $time;
?>
</span>
</div>
<div>
<strong>Status:</strong><br>
<span style="color: <?php echo $linking_module->get_setting('enabled') === '1' ? '#00a32a' : '#d63638'; ?>;">
<?php echo $linking_module->get_setting('enabled') === '1' ? '✅ Active' : '❌ Paused'; ?>
</span>
</div>
<div>
<strong>Articles per Run:</strong><br>
<span style="color: #666;"><?php echo $linking_module->get_setting('process_articles_per_run', '5'); ?> articles</span>
</div>
<div>
<button type="button" class="button button-primary" onclick="document.getElementById('scheduler-controls').style.display='block';">⚙️ Edit Schedule</button>
<form method="post" style="display: inline; margin-left: 10px;">
<input type="hidden" name="internal_linking_action" value="save_settings">
<input type="hidden" name="enabled" value="<?php echo $linking_module->get_setting('enabled') === '1' ? '0' : '1'; ?>">
<?php wp_nonce_field('internal_linking_settings', 'internal_linking_nonce'); ?>
<button type="submit" class="button <?php echo $linking_module->get_setting('enabled') === '1' ? 'button-secondary' : 'button-primary'; ?>">
<?php echo $linking_module->get_setting('enabled') === '1' ? '⏸️ Pause' : '▶️ Resume'; ?>
</button>
</form>
</div>
</div>
<!-- Quick Edit Controls (Hidden by default) -->
<div id="scheduler-controls" style="display: none; margin-top: 20px; padding-top: 20px; border-top: 1px solid #ddd;">
<form method="post" action="">
<input type="hidden" name="internal_linking_action" value="save_settings">
<?php wp_nonce_field('internal_linking_settings', 'internal_linking_nonce'); ?>
<!-- Basic Scheduler Settings -->
<h4 style="margin: 0 0 15px 0; color: #2271b1;">Scheduler Settings</h4>
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; align-items: end; margin-bottom: 25px;">
<div>
<label><strong>Frequency:</strong></label>
<select name="scheduler_frequency" class="regular-text">
<option value="daily" <?php selected($linking_module->get_setting('scheduler_frequency'), 'daily'); ?>>Daily</option>
<option value="weekly" <?php selected($linking_module->get_setting('scheduler_frequency'), 'weekly'); ?>>Weekly</option>
<option value="monthly" <?php selected($linking_module->get_setting('scheduler_frequency'), 'monthly'); ?>>Monthly</option>
</select>
</div>
<div>
<label><strong>Time:</strong></label>
<input type="time" name="scheduler_time" value="<?php echo $linking_module->get_setting('scheduler_time', '03:00'); ?>" class="regular-text">
</div>
<div>
<label><strong>Articles per Run:</strong></label>
<input type="number" name="process_articles_per_run" min="1" max="50" value="<?php echo $linking_module->get_setting('process_articles_per_run', '5'); ?>" class="small-text">
</div>
<div>
<label><strong>Old Article Days:</strong></label>
<input type="number" name="old_article_days_limit" min="30" max="365" value="<?php echo $linking_module->get_setting('old_article_days_limit', '180'); ?>" class="small-text">
</div>
</div>
<!-- Link Limits -->
<h4 style="margin: 0 0 15px 0; color: #2271b1;">Link Limits</h4>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; align-items: end; margin-bottom: 25px;">
<div>
<label><strong>Max Links per Article:</strong></label>
<input type="number" name="max_links_per_article" min="1" max="20" value="<?php echo $linking_module->get_setting('max_links_per_article', '5'); ?>" class="small-text">
</div>
<div>
<label><strong>Max New Links per Session:</strong></label>
<input type="number" name="max_new_links_per_session" min="1" max="10" value="<?php echo $linking_module->get_setting('max_new_links_per_session', '3'); ?>" class="small-text">
</div>
<div>
<label><strong>Min Article Word Count:</strong></label>
<input type="number" name="min_article_word_count" min="100" max="1000" value="<?php echo $linking_module->get_setting('min_article_word_count', '300'); ?>" class="small-text">
</div>
</div>
<!-- Quality Thresholds -->
<h4 style="margin: 0 0 15px 0; color: #2271b1;">Quality Thresholds</h4>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; align-items: end; margin-bottom: 25px;">
<div>
<label><strong>Min Relevance Score (%):</strong></label>
<input type="number" name="min_relevance_score" min="50" max="100" value="<?php echo $linking_module->get_setting('min_relevance_score', '75'); ?>" class="small-text">
</div>
<div>
<label><strong>Auto-Approve Threshold (%):</strong></label>
<input type="number" name="auto_approve_threshold" min="70" max="100" value="<?php echo $linking_module->get_setting('auto_approve_threshold', '90'); ?>" class="small-text">
</div>
<div>
<label><strong>Automation Mode:</strong></label>
<select name="automation_mode" class="regular-text">
<option value="auto" <?php selected($linking_module->get_setting('automation_mode'), 'auto'); ?>>Full Auto</option>
<option value="preview" <?php selected($linking_module->get_setting('automation_mode'), 'preview'); ?>>Preview Mode</option>
<option value="hybrid" <?php selected($linking_module->get_setting('automation_mode'), 'hybrid'); ?>>Hybrid</option>
</select>
</div>
</div>
<!-- Action Buttons -->
<div style="text-align: center; padding-top: 20px; border-top: 1px solid #ddd;">
<button type="submit" class="button button-primary button-large">💾 Save All Settings</button>
<button type="button" class="button button-secondary" onclick="document.getElementById('scheduler-controls').style.display='none';" style="margin-left: 10px;">Cancel</button>
</div>
</form>
</div>
</div>
<!-- Live Process Log Window -->
<h3>📋 Process Log</h3>
<div style="background: #1e1e1e; color: #00ff00; padding: 15px; border-radius: 8px; font-family: 'Courier New', monospace; font-size: 12px; height: 200px; overflow-y: auto; margin-bottom: 30px; border: 1px solid #444;">
<div id="process-log">
<?php
// Get recent log entries from Finnish_Global_Logger
$all_logs = Finnish_Global_Logger::get_logs();
$internal_linking_logs = array_filter($all_logs, function($log) {
return stripos($log, 'Internal Linking') !== false;
});
if (!empty($internal_linking_logs)) {
// Show most recent 20 logs, reverse order so newest is at bottom
$recent_logs = array_slice($internal_linking_logs, 0, 20);
foreach (array_reverse($recent_logs) as $entry) {
echo esc_html($entry) . "\n";
}
} else {
// Show system status if no logs exist yet
echo '[' . current_time('Y-m-d H:i:s') . '] Internal Linking Intelligence: System initialized' . "\n";
echo '[' . current_time('Y-m-d H:i:s') . '] Database Tables: Ready' . "\n";
echo '[' . current_time('Y-m-d H:i:s') . '] Configuration: ' . ($linking_module->get_setting('enabled') === '1' ? 'Active' : 'Paused') . "\n";
echo '[' . current_time('Y-m-d H:i:s') . '] Automation Mode: ' . ucfirst($linking_module->get_setting('automation_mode', 'preview')) . "\n";
echo '[' . current_time('Y-m-d H:i:s') . '] Ready for processing. Try "Run Now" or save settings to see logs.' . "\n";
}
?>
</div>
</div>
<?php
// Enhanced Link History with Pagination
$page = isset($_GET['history_page']) ? max(1, intval($_GET['history_page'])) : 1;
$per_page = 20;
$offset = ($page - 1) * $per_page;
// Get total count for pagination
global $wpdb;
$total_links = $wpdb->get_var("SELECT COUNT(*) FROM {$linking_module->table_linking_history} WHERE status = 'active'");
$max_pages = ceil($total_links / $per_page);
// Get paginated history with better query
$history = $wpdb->get_results($wpdb->prepare("
SELECT h.*,
s.post_title as source_title,
t.post_title as target_title,
s.post_date as source_date,
t.post_date as target_date
FROM {$linking_module->table_linking_history} h
LEFT JOIN {$wpdb->posts} s ON h.source_post_id = s.ID
LEFT JOIN {$wpdb->posts} t ON h.target_post_id = t.ID
WHERE h.status = 'active'
ORDER BY h.inserted_date DESC
LIMIT %d OFFSET %d
", $per_page, $offset));
?>
<h3>📊 Link History (<?php echo number_format($total_links); ?> total links)</h3>
<?php if (!empty($history)) : ?>
<!-- Pagination Controls -->
<div style="margin-bottom: 15px; display: flex; justify-content: space-between; align-items: center;">
<div>
<span style="color: #666;">
Showing <?php echo number_format($offset + 1); ?>-<?php echo number_format(min($offset + $per_page, $total_links)); ?>
of <?php echo number_format($total_links); ?> links
</span>
</div>
<div>
<?php if ($page > 1) : ?>
<a href="?page=finnish-network-global&tab=internal_linking&history_page=<?php echo $page - 1; ?>" class="button">« Previous</a>
<?php endif; ?>
<span style="margin: 0 10px;">Page <?php echo $page; ?> of <?php echo $max_pages; ?></span>
<?php if ($page < $max_pages) : ?>
<a href="?page=finnish-network-global&tab=internal_linking&history_page=<?php echo $page + 1; ?>" class="button">Next »</a>
<?php endif; ?>
</div>
</div>
<div style="background: #fff; border-radius: 8px; border: 1px solid #c3c4c7; overflow: hidden;">
<table class="widefat">
<thead style="background: #f6f7f7;">
<tr>
<th style="width: 120px;">Date Created</th>
<th>Source Article</th>
<th style="width: 50px; text-align: center;">→</th>
<th style="width: 150px; text-align: center;">Anchor Text</th>
<th style="width: 50px; text-align: center;">→</th>
<th>Target Article</th>
<th style="width: 80px; text-align: center;">Relevance</th>
<th style="width: 100px; text-align: center;">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($history as $link) : ?>
<tr style="border-bottom: 1px solid #f0f0f0;">
<td style="font-size: 11px; color: #666;">
<?php echo date('M j, Y', strtotime($link->inserted_date)); ?><br>
<small><?php echo date('H:i', strtotime($link->inserted_date)); ?></small>
</td>
<td>
<strong><a href="<?php echo get_edit_post_link($link->source_post_id); ?>" target="_blank" style="text-decoration: none;">
<?php echo esc_html(wp_trim_words($link->source_title, 8)); ?>
</a></strong>
<br><small style="color: #666;"><?php echo date('M j, Y', strtotime($link->source_date)); ?></small>
</td>
<td style="text-align: center; color: #2271b1; font-size: 16px; font-weight: bold;">→</td>
<td style="background: #fffbcc; font-weight: bold; text-align: center; padding: 8px;">
"<?php echo esc_html(wp_trim_words($link->anchor_text, 4)); ?>"
</td>
<td style="text-align: center; color: #2271b1; font-size: 16px; font-weight: bold;">→</td>
<td>
<strong><a href="<?php echo get_permalink($link->target_post_id); ?>" target="_blank" style="text-decoration: none;">
<?php echo esc_html(wp_trim_words($link->target_title, 8)); ?>
</a></strong>
<br><small style="color: #666;"><?php echo date('M j, Y', strtotime($link->target_date)); ?></small>
</td>
<td style="text-align: center;">
<span style="padding: 4px 8px; border-radius: 4px; color: white; font-weight: bold; background: <?php echo $link->relevance_score >= 90 ? '#00a32a' : ($link->relevance_score >= 75 ? '#f0b849' : '#d63638'); ?>;">
<?php echo round($link->relevance_score); ?>%
</span>
</td>
<td style="text-align: center;">
<form method="post" style="display: inline;">
<input type="hidden" name="internal_linking_action" value="remove_link">
<input type="hidden" name="history_id" value="<?php echo $link->id; ?>">
<?php wp_nonce_field('internal_linking_remove', 'internal_linking_nonce'); ?>
<button type="submit" class="button button-secondary button-small" onclick="return confirm('Remove this link from the article?');" title="Remove Link">🗑️</button>
</form>
<a href="<?php echo get_edit_post_link($link->source_post_id); ?>" target="_blank" class="button button-small" title="Edit Source" style="margin-left: 2px;">✏️</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- Bottom Pagination -->
<div style="margin-top: 15px; text-align: center;">
<?php if ($max_pages > 1) : ?>
<?php for ($i = max(1, $page - 2); $i <= min($max_pages, $page + 2); $i++) : ?>
<?php if ($i == $page) : ?>
<span class="button button-primary" style="margin: 0 2px;"><?php echo $i; ?></span>
<?php else : ?>
<a href="?page=finnish-network-global&tab=internal_linking&history_page=<?php echo $i; ?>" class="button" style="margin: 0 2px;"><?php echo $i; ?></a>
<?php endif; ?>
<?php endfor; ?>
<?php endif; ?>
</div>
<?php else : ?>
<div style="background: #f8f9fa; padding: 40px; border-radius: 8px; text-align: center; color: #666;">
<h4>No links created yet</h4>
<p>Use the "Run Now" button above to start processing articles and creating internal links.</p>
</div>
<?php endif; ?>
<?php
// Top Linked Articles
if (isset($stats['top_targets']) && !empty($stats['top_targets'])) :
?>
<h3>🏆 Most Linked Articles</h3>
<div style="background: #fff; padding: 20px; border-radius: 8px; border: 1px solid #c3c4c7;">
<table class="widefat">
<thead>
<tr>
<th>Article</th>
<th>Incoming Links</th>
<th>View</th>
</tr>
</thead>
<tbody>
<?php foreach ($stats['top_targets'] as $target) : ?>
<tr>
<td><?php echo esc_html($target->post_title); ?></td>
<td><strong><?php echo $target->link_count; ?></strong> links</td>
<td>
<a href="<?php echo get_permalink($target->target_post_id); ?>" target="_blank" class="button button-small">View Post</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<?php else : ?>
<div class="notice notice-error">
<p>Internal Linking Intelligence module not loaded. Please check the module file exists and has no errors.</p>
</div>
<?php endif; ?>
<!-- Comprehensive How It Works Guide -->
<div style="margin-top: 50px; padding: 30px; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-radius: 12px; border: 1px solid #dee2e6;">
<h2 style="color: #2271b1; margin-bottom: 25px; font-size: 24px;">🧠 How Internal Linking Intelligence Works</h2>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 30px; margin-bottom: 40px;">
<!-- Process Flow -->
<div>
<h3 style="color: #135e96; margin-bottom: 15px;">📋 Step-by-Step Process</h3>
<ol style="line-height: 1.8; color: #333;">
<li><strong>Article Detection:</strong> System scans for new articles published since last run</li>
<li><strong>Content Analysis:</strong> Extracts key phrases, topics, and themes from new articles</li>
<li><strong>Old Article Search:</strong> Finds older articles (within <?php echo $linking_module->get_setting('old_article_days_limit', '180'); ?> days) that could link to new content</li>
<li><strong>Phrase Matching:</strong> Searches for exact matches of key phrases in old article content</li>
<li><strong>Relevance Scoring:</strong> Calculates how relevant each potential link is (0-100%)</li>
<li><strong>Smart Insertion:</strong> Makes existing phrases clickable (no new text added)</li>
<li><strong>Quality Control:</strong> Ensures links meet minimum standards before insertion</li>
</ol>
</div>
<!-- Key Concepts -->
<div>
<h3 style="color: #135e96; margin-bottom: 15px;">🎯 Key Concepts</h3>
<div style="background: white; padding: 15px; border-radius: 8px; border: 1px solid #ddd;">
<p style="margin: 0 0 10px;"><strong>Existing Phrase Linking:</strong> Only existing text becomes clickable - no content changes!</p>
<p style="margin: 0 0 10px;"><strong>Relevance Scoring:</strong> Combines category match, tag overlap, content similarity, and position</p>
<p style="margin: 0 0 10px;"><strong>Safe Processing:</strong> Respects link limits, avoids over-optimization, preserves content flow</p>
<p style="margin: 0;"><strong>Smart Distribution:</strong> Spreads links across multiple old articles, not all in one</p>
</div>
</div>
</div>
<!-- Settings Explained -->
<div style="margin-bottom: 40px;">
<h3 style="color: #135e96; margin-bottom: 20px;">⚙️ Settings Explained in Detail</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
<div style="background: white; padding: 20px; border-radius: 8px; border: 1px solid #ddd;">
<h4 style="color: #2271b1; margin-bottom: 10px;">🔢 Processing Limits</h4>
<p><strong>Articles per Run (<?php echo $linking_module->get_setting('process_articles_per_run', '5'); ?>):</strong> How many new articles to process each time the system runs. Higher = more links created, but slower processing.</p>
<p><strong>Max Links per Article (<?php echo $linking_module->get_setting('max_links_per_article', '5'); ?>):</strong> Total internal links allowed in any single article to prevent over-optimization and SEO penalties.</p>
<p><strong>Max New Links per Session (<?php echo $linking_module->get_setting('max_new_links_per_session', '3'); ?>):</strong> Maximum new links to add to any article in one processing run.</p>
</div>
<div style="background: white; padding: 20px; border-radius: 8px; border: 1px solid #ddd;">
<h4 style="color: #2271b1; margin-bottom: 10px;">🎯 Quality Thresholds</h4>
<p><strong>Min Relevance Score (<?php echo $linking_module->get_setting('min_relevance_score', '75'); ?>%):</strong> Only links with this relevance or higher will be created. Higher = more selective, fewer but better links.</p>
<p><strong>Auto-Approve Threshold (<?php echo $linking_module->get_setting('auto_approve_threshold', '90'); ?>%):</strong> In hybrid mode, links above this score get inserted automatically. Below goes to approval queue.</p>
<p><strong>Look Back Days (<?php echo $linking_module->get_setting('old_article_days_limit', '180'); ?>):</strong> How far back to search for old articles. Newer = more relevant, older = more content to link from.</p>
</div>
</div>
</div>
<!-- Automation Modes -->
<div style="margin-bottom: 40px;">
<h3 style="color: #135e96; margin-bottom: 20px;">🤖 Automation Modes Explained</h3>
<?php $current_mode = $linking_module->get_setting('automation_mode', 'preview'); ?>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px;">
<div style="background: <?php echo $current_mode === 'auto' ? '#e7f3ff' : 'white'; ?>; padding: 15px; border-radius: 8px; border: 2px solid <?php echo $current_mode === 'auto' ? '#2271b1' : '#ddd'; ?>;">
<h4 style="color: #2271b1; margin-bottom: 8px;">⚡ Full Auto</h4>
<p style="margin: 0; font-size: 14px;">Links are inserted immediately if they meet minimum relevance threshold. Fastest but least control.</p>
<?php if ($current_mode === 'auto') echo '<p style="margin: 8px 0 0; color: #2271b1; font-weight: bold;">← CURRENT MODE</p>'; ?>
</div>
<div style="background: <?php echo $current_mode === 'preview' ? '#e7f3ff' : 'white'; ?>; padding: 15px; border-radius: 8px; border: 2px solid <?php echo $current_mode === 'preview' ? '#2271b1' : '#ddd'; ?>;">
<h4 style="color: #2271b1; margin-bottom: 8px;">👀 Preview Mode</h4>
<p style="margin: 0; font-size: 14px;">All links go to approval queue for manual review. Maximum control but requires your time to approve.</p>
<?php if ($current_mode === 'preview') echo '<p style="margin: 8px 0 0; color: #2271b1; font-weight: bold;">← CURRENT MODE</p>'; ?>
</div>
<div style="background: <?php echo $current_mode === 'hybrid' ? '#e7f3ff' : 'white'; ?>; padding: 15px; border-radius: 8px; border: 2px solid <?php echo $current_mode === 'hybrid' ? '#2271b1' : '#ddd'; ?>;">
<h4 style="color: #2271b1; margin-bottom: 8px;">⚖️ Hybrid</h4>
<p style="margin: 0; font-size: 14px;">High-confidence links (90%+) insert automatically. Lower-confidence links need approval. Best of both worlds.</p>
<?php if ($current_mode === 'hybrid') echo '<p style="margin: 8px 0 0; color: #2271b1; font-weight: bold;">← CURRENT MODE</p>'; ?>
</div>
</div>
</div>
<!-- When It Runs -->
<div style="margin-bottom: 40px;">
<h3 style="color: #135e96; margin-bottom: 20px;">⏰ When & How Often It Processes</h3>
<div style="background: white; padding: 20px; border-radius: 8px; border: 1px solid #ddd;">
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px;">
<div>
<h4 style="color: #2271b1; margin-bottom: 8px;">Current Schedule</h4>
<p style="margin: 0; font-size: 16px; font-weight: bold;"><?php echo ucfirst($linking_module->get_setting('scheduler_frequency', 'weekly')); ?> at <?php echo $linking_module->get_setting('scheduler_time', '03:00'); ?></p>
</div>
<div>
<h4 style="color: #2271b1; margin-bottom: 8px;">Last Run</h4>
<p style="margin: 0;"><?php
$last_run = $linking_module->get_setting('last_run_date');
echo $last_run ? human_time_diff(strtotime($last_run)) . ' ago' : 'Never';
?></p>
</div>
<div>
<h4 style="color: #2271b1; margin-bottom: 8px;">Status</h4>
<p style="margin: 0; font-weight: bold; color: <?php echo $linking_module->get_setting('enabled') === '1' ? '#00a32a' : '#d63638'; ?>;">
<?php echo $linking_module->get_setting('enabled') === '1' ? '✅ Active' : '❌ Paused'; ?>
</p>
</div>
</div>
<div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #eee;">
<p style="margin: 0; color: #666; font-style: italic;">
<strong>How it chooses when to run:</strong> System tracks the last processing date and only runs when new articles have been published since then.
If no new articles exist, it skips processing to save resources.
</p>
</div>
</div>
</div>
<!-- Relevance Scoring Deep Dive -->
<div style="margin-bottom: 40px;">
<h3 style="color: #135e96; margin-bottom: 20px;">📊 How Relevance Scoring Works (0-100%)</h3>
<div style="background: white; padding: 20px; border-radius: 8px; border: 1px solid #ddd;">
<p style="margin-bottom: 15px;">The system calculates relevance by combining multiple factors to ensure only high-quality, contextually appropriate links are created:</p>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px;">
<div>
<h4 style="color: #2271b1; margin-bottom: 10px;">📈 Scoring Factors</h4>
<ul style="line-height: 1.8; margin: 0;">
<li><strong>Base Phrase Weight (0-100):</strong> How important the matched phrase is (title words = 100, content frequency = 50-80)</li>
<li><strong>Category Match Bonus (+10):</strong> Same category between old and new article</li>
<li><strong>Tag Match Bonus (+5):</strong> Shared tags between articles</li>
<li><strong>Position Bonus (+5):</strong> Phrase appears in first 30% of old article content</li>
<li><strong>Semantic Similarity (+10):</strong> Articles share many common meaningful words</li>
</ul>
</div>
<div>
<h4 style="color: #2271b1; margin-bottom: 10px;">🎯 Score Examples</h4>
<div style="font-family: monospace; font-size: 13px; background: #f8f9fa; padding: 10px; border-radius: 4px;">
<div style="color: #00a32a; margin-bottom: 5px;"><strong>95% - Excellent:</strong> Title phrase + same category + shared tags</div>
<div style="color: #f0b849; margin-bottom: 5px;"><strong>82% - Good:</strong> Frequent content phrase + category match</div>
<div style="color: #d63638;"><strong>65% - Poor:</strong> Single word match + no category overlap</div>
</div>
<p style="margin: 10px 0 0; font-size: 13px; color: #666;">Only scores ≥ <?php echo $linking_module->get_setting('min_relevance_score', '75'); ?>% create links</p>
</div>
</div>
</div>
</div>
<!-- Safety & Best Practices -->
<div style="margin-bottom: 30px;">
<h3 style="color: #135e96; margin-bottom: 20px;">🛡️ Built-in Safety & SEO Protection</h3>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px;">
<div style="background: white; padding: 15px; border-radius: 8px; border: 1px solid #ddd;">
<h4 style="color: #d63638; margin-bottom: 8px;">🚫 Over-Optimization Prevention</h4>
<ul style="margin: 0; font-size: 13px; line-height: 1.6;">
<li>Max <?php echo $linking_module->get_setting('max_links_per_article', '5'); ?> total internal links per article</li>
<li>Only <?php echo $linking_module->get_setting('max_new_links_per_session', '3'); ?> new links per session</li>
<li>Skips articles already at link limit</li>
<li>No duplicate links (same source→target)</li>
</ul>
</div>
<div style="background: white; padding: 15px; border-radius: 8px; border: 1px solid #ddd;">
<h4 style="color: #2271b1; margin-bottom: 8px;">✅ Content Protection</h4>
<ul style="margin: 0; font-size: 13px; line-height: 1.6;">
<li>Never modifies existing links</li>
<li>Preserves WordPress block structure</li>
<li>Only makes existing text clickable</li>
<li>Full rollback capability per link</li>
</ul>
</div>
<div style="background: white; padding: 15px; border-radius: 8px; border: 1px solid #ddd;">
<h4 style="color: #00a32a; margin-bottom: 8px;">🎯 Smart Distribution</h4>
<ul style="margin: 0; font-size: 13px; line-height: 1.6;">
<li>Spreads links across multiple old articles</li>
<li>Prioritizes high-traffic source posts</li>
<li>Balances link equity distribution</li>
<li>Tracks and analyzes performance</li>
</ul>
</div>
</div>
</div>
<!-- Performance & Troubleshooting -->
<div>
<h3 style="color: #135e96; margin-bottom: 20px;">🔧 Performance Tips & Troubleshooting</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
<div style="background: white; padding: 20px; border-radius: 8px; border: 1px solid #ddd;">
<h4 style="color: #2271b1; margin-bottom: 10px;">⚡ Optimization Tips</h4>
<ul style="margin: 0; line-height: 1.8;">
<li><strong>Start conservative:</strong> Begin with 3-5 articles per run, increase gradually</li>
<li><strong>Monitor performance:</strong> Check processing time in logs</li>
<li><strong>Adjust time limits:</strong> Run during low-traffic hours (3 AM default)</li>
<li><strong>Use hybrid mode:</strong> Best balance of automation and control</li>
</ul>
</div>
<div style="background: white; padding: 20px; border-radius: 8px; border: 1px solid #ddd;">
<h4 style="color: #d63638; margin-bottom: 10px;">🐛 Common Issues</h4>
<ul style="margin: 0; line-height: 1.8;">
<li><strong>No links created:</strong> Check relevance threshold (lower if too high)</li>
<li><strong>Poor quality links:</strong> Increase minimum relevance score</li>
<li><strong>Too many pending:</strong> Use auto or hybrid mode instead of preview</li>
<li><strong>Slow processing:</strong> Reduce articles per run or look-back days</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
</div>
</div>
<?php
}
function finnish_global_render_feed_meta_box( $post ) {
$feed_url = get_post_meta( $post->ID, '_feed_url', true );
wp_nonce_field( 'finnish_global_save_feed_data', 'finnish_global_feed_nonce' );
?>
<p>
<label for="finnish_global_feed_url" style="font-weight: bold;">RSS Feed URL:</label><br>
<input type="url" id="finnish_global_feed_url" name="finnish_global_feed_url" value="<?php echo esc_attr( $feed_url ); ?>" class="widefat" placeholder="https://example.com/feed.xml">
<span class="description">Enter the full URL of the RSS feed you want to monitor.</span>
</p>
<?php
}
function finnish_global_save_feed_meta_box( $post_id ) {
if ( ! isset( $_POST['finnish_global_feed_nonce'] ) ) {
return;
}
if ( ! wp_verify_nonce( $_POST['finnish_global_feed_nonce'], 'finnish_global_save_feed_data' ) ) {
return;
}
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
if ( isset( $_POST['finnish_global_feed_url'] ) ) {
update_post_meta( $post_id, '_feed_url', sanitize_text_field( $_POST['finnish_global_feed_url'] ) );
}
}
// Add Meta Box for Original Link (Post Editor)
add_action( 'add_meta_boxes', 'finnish_global_add_original_link_meta_box' );
function finnish_global_add_original_link_meta_box() {
add_meta_box(
'finnish_global_original_link',
'Original Article Source',
'finnish_global_render_original_link_meta_box',
'post',
'side',
'default'
);
}
function finnish_global_render_original_link_meta_box( $post ) {
$original_link = get_post_meta( $post->ID, '_original_rss_link', true );
if ( ! empty( $original_link ) ) {
echo '<p><a href="' . esc_url( $original_link ) . '" target="_blank" class="button button-secondary">View Original Article</a></p>';
echo '<p class="description" style="word-break: break-all;">' . esc_url( $original_link ) . '</p>';
} else {
echo '<p>No original source linked.</p>';
}
}
function finnish_global_render_text_field( $args ) {
$name = $args['name'];
$value = get_option( $name );
$description = isset( $args['description'] ) ? $args['description'] : '';
// Check if this is an API key or password field that should be masked
$sensitive_fields = array(
'finnish_global_openai_key',
'finnish_global_pexels_key',
'finnish_global_scraper_api_key',
'finnish_global_dataforseo_key',
'finnish_global_dataforseo_login',
'finnish_global_perplexity_api',
'finnish_global_slack_bot_token',
'finnish_global_slack_webhook_url',
'finnish_global_cronjob_api_key'
);
if ( in_array( $name, $sensitive_fields ) && !empty( $value ) ) {
// Mask the value showing only last 4 characters
$value_length = strlen( $value );
if ( $value_length > 4 ) {
$masked_value = str_repeat( '•', $value_length - 4 ) . substr( $value, -4 );
} else {
$masked_value = str_repeat( '•', $value_length );
}
// Use a hidden field for the actual value and show masked version
echo '<input type="password" id="' . esc_attr( $name ) . '" name="' . esc_attr( $name ) . '" value="' . esc_attr( $value ) . '" class="regular-text" style="font-family: monospace;">';
echo '<span class="description" style="margin-left: 10px;">Current: ' . esc_html( $masked_value ) . '</span>';
} else {
echo '<input type="text" id="' . esc_attr( $name ) . '" name="' . esc_attr( $name ) . '" value="' . esc_attr( $value ) . '" class="regular-text">';
}
if ( ! empty( $description ) ) {
echo '<p class="description">' . $description . '</p>';
}
}
function finnish_global_render_checkbox_field( $args ) {
$name = $args['name'];
$value = get_option( $name );
$description = isset( $args['description'] ) ? $args['description'] : '';
echo '<input type="checkbox" id="' . esc_attr( $name ) . '" name="' . esc_attr( $name ) . '" value="1" ' . checked( 1, $value, false ) . '>';
if ( ! empty( $description ) ) {
echo '<p class="description">' . $description . '</p>';
}
}
function finnish_global_render_target_market_field() {
$value = get_option( 'finnish_global_target_market', 'fi_FI' );
$markets = array(
'fi_FI' => 'Finnish (Finland)',
'en_US' => 'English (USA)',
'en_GB' => 'English (UK)',
'sv_SE' => 'Swedish (Sweden)',
'de_DE' => 'German (Germany)',
'es_ES' => 'Spanish (Spain)',
'no_NO' => 'Norwegian (Norway)',
'tl_PH' => 'Filipino (Philippines)',
'id_ID' => 'Indonesian (Indonesia)',
'ms_MY' => 'Malay (Malaysia)',
'th_TH' => 'Thai (Thailand)',
'vi_VN' => 'Vietnamese (Vietnam)',
'ja_JP' => 'Japanese (Japan)',
);
echo '<select name="finnish_global_target_market">';
foreach ( $markets as $code => $label ) {
echo '<option value="' . esc_attr( $code ) . '" ' . selected( $value, $code, false ) . '>' . esc_html( $label ) . '</option>';
}
echo '</select>';
echo '<p class="description">Select the target language and country for AI generation and SEO research.</p>';
}
function finnish_global_render_slack_channel_field() {
echo '<p class="description"><strong>Option 1: Webhook URL (Recommended - Simpler)</strong></p>';
$webhook_url = get_option( 'finnish_global_slack_webhook_url' );
if ( !empty( $webhook_url ) ) {
$url_length = strlen( $webhook_url );
if ( $url_length > 4 ) {
$masked_url = str_repeat( '•', $url_length - 4 ) . substr( $webhook_url, -4 );
} else {
$masked_url = str_repeat( '•', $url_length );
}
echo '<input type="password" name="finnish_global_slack_webhook_url" value="' . esc_attr( $webhook_url ) . '" placeholder="https://hooks.slack.com/services/..." class="large-text" style="font-family: monospace;">';
echo '<span class="description" style="margin-left: 10px;">Current: ' . esc_html( $masked_url ) . '</span>';
} else {
echo '<input type="text" name="finnish_global_slack_webhook_url" value="" placeholder="https://hooks.slack.com/services/..." class="large-text">';
}
echo '<p class="description">Get webhook URL: Slack → Apps → Incoming Webhooks → Add to Slack → Choose channel → Copy URL</p>';
echo '<p class="description" style="margin-top: 15px;"><strong>Option 2: Channel ID (Advanced - Requires API Token)</strong></p>';
$channel_id = get_option( 'finnish_global_slack_channel_id' );
echo '<input type="text" name="finnish_global_slack_channel_id" value="' . esc_attr( $channel_id ) . '" placeholder="C1234567890" class="regular-text">';
echo '<p class="description">Only use if you have API token configured. Find Channel ID by opening channel → Click name → Copy ID</p>';
}
function finnish_global_render_slack_triggers_field() {
$triggers = get_option( 'finnish_global_slack_triggers', array() );
if ( ! is_array( $triggers ) ) {
$triggers = array();
}
?>
<fieldset>
<label>
<input type="checkbox" name="finnish_global_slack_triggers[]" value="publish" <?php checked( in_array( 'publish', $triggers ) ); ?>>
New Post Published
</label><br>
<label>
<input type="checkbox" name="finnish_global_slack_triggers[]" value="draft" <?php checked( in_array( 'draft', $triggers ) ); ?>>
New Draft Created
</label><br>
<label>
<input type="checkbox" name="finnish_global_enable_evergreen_slack" value="1" <?php checked( get_option( 'finnish_global_enable_evergreen_slack', false ) ); ?>>
Evergreen Reviver Updates
</label>
</fieldset>
<?php
}
function finnish_global_render_license_field() {
$license_key = get_option( 'finnish_global_license_key' );
$license_manager = new Finnish_Global_License_Manager();
echo '<input type="text" name="finnish_global_license_key" value="' . esc_attr( $license_key ) . '" placeholder="XXXXX-XXXXX-XXXXX" class="regular-text">';
// Show validation status
if (!empty($license_key)) {
$result = $license_manager->test_license_key($license_key);
if ($result['valid']) {
echo '<p class="description" style="color: green;">✅ <strong>License Valid!</strong> Your plugin is activated.</p>';
} else {
echo '<p class="description" style="color: red;">❌ <strong>Invalid License:</strong> ' . esc_html($result['message']) . '</p>';
}
} else {
$remaining = $license_manager->get_grace_period_remaining();
if ($remaining > 0) {
echo '<p class="description" style="color: orange;">⚠️ <strong>Grace Period:</strong> ' . $remaining . ' hours remaining. Please enter your license key.</p>';
} else {
echo '<p class="description" style="color: red;">❌ <strong>Grace Period Expired:</strong> Schedulers are disabled. Please enter your license key.</p>';
}
}
echo '<p class="description">Enter the license key provided by your administrator. Contact support if you need a license.</p>';
}
// AI Enhancement Settings Callback Functions
function finnish_global_render_perplexity_key_field() {
$value = get_option( 'finnish_global_perplexity_key', '' );
if ( !empty( $value ) ) {
// Mask the value showing only last 4 characters
$value_length = strlen( $value );
if ( $value_length > 4 ) {
$masked_value = str_repeat( '•', $value_length - 4 ) . substr( $value, -4 );
} else {
$masked_value = str_repeat( '•', $value_length );
}
echo '<input type="password" name="finnish_global_perplexity_key" value="' . esc_attr( $value ) . '" class="regular-text" style="font-family: monospace;" />';
echo '<span class="description" style="margin-left: 10px;">Current: ' . esc_html( $masked_value ) . '</span>';
} else {
echo '<input type="password" name="finnish_global_perplexity_key" value="' . esc_attr( $value ) . '" class="regular-text" />';
}
echo '<p class="description">Get your API key from <a href="https://www.perplexity.ai/" target="_blank">Perplexity AI</a></p>';
}
function finnish_global_render_enable_perplexity_field() {
$value = get_option( 'finnish_global_enable_perplexity', false );
echo '<input type="checkbox" name="finnish_global_enable_perplexity" value="1" ' . checked( 1, $value, false ) . ' />';
echo '<label> Uses Perplexity AI to fact-check content and insert authority links</label>';
}
function finnish_global_render_perplexity_model_field() {
$value = get_option( 'finnish_global_perplexity_model', 'sonar' );
$models = array(
'sonar' => 'Sonar (Recommended - Fast & Accurate)',
'sonar-pro' => 'Sonar Pro (Most Capable)'
);
echo '<select name="finnish_global_perplexity_model">';
foreach ( $models as $model_id => $label ) {
echo '<option value="' . esc_attr( $model_id ) . '" ' . selected( $value, $model_id, false ) . '>' . esc_html( $label ) . '</option>';
}
echo '</select>';
echo '<p class="description">Choose the Perplexity model for fact-checking</p>';
}
function finnish_global_render_perplexity_facts_field() {
$value = get_option( 'finnish_global_perplexity_contextual_facts', 2 );
echo '<input type="number" name="finnish_global_perplexity_contextual_facts" value="' . esc_attr( $value ) . '" min="0" max="5" class="small-text" />';
echo '<p class="description">Number of facts to weave naturally into content (0-5)</p>';
}
function finnish_global_render_perplexity_inline_field() {
$value = get_option( 'finnish_global_perplexity_inline_links', 3 );
echo '<input type="number" name="finnish_global_perplexity_inline_links" value="' . esc_attr( $value ) . '" min="0" max="10" class="small-text" />';
echo '<p class="description">Number of clickable authority links to add in content (0-10)</p>';
}
function finnish_global_render_perplexity_footnote_field() {
$value = get_option( 'finnish_global_perplexity_footnote_links', 3 );
echo '<input type="number" name="finnish_global_perplexity_footnote_links" value="' . esc_attr( $value ) . '" min="0" max="10" class="small-text" />';
echo '<p class="description">Number of reference links to add at bottom (0-10)</p>';
}
function finnish_global_render_perplexity_link_style_field() {
$value = get_option( 'finnish_global_perplexity_link_style', 'both' );
$styles = array(
'contextual' => 'Contextual Only (weaved in text)',
'footnote' => 'Footnote Only (references at bottom)',
'both' => 'Both (contextual + footnote)'
);
echo '<select name="finnish_global_perplexity_link_style">';
foreach ( $styles as $style_id => $label ) {
echo '<option value="' . esc_attr( $style_id ) . '" ' . selected( $value, $style_id, false ) . '>' . esc_html( $label ) . '</option>';
}
echo '</select>';
echo '<p class="description">How to display authority links</p>';
}
function finnish_global_render_image_strategy_field() {
$value = get_option( 'finnish_global_image_strategy', 'pexels_ai_fallback' );
$strategies = array(
'pexels_ai_fallback' => 'Pexels Primary + AI Fallback (article context + smart fallback)',
'ai_pexels_fallback' => 'AI Primary + Pexels Fallback (full article context + stock fallback)',
'pexels_only' => 'Pexels Only (no AI)',
'disabled' => 'Disabled (no images)'
);
echo '<select name="finnish_global_image_strategy">';
foreach ( $strategies as $strategy_id => $label ) {
echo '<option value="' . esc_attr( $strategy_id ) . '" ' . selected( $value, $strategy_id, false ) . '>' . esc_html( $label ) . '</option>';
}
echo '</select>';
echo '<p class="description">Choose how images are generated for articles</p>';
}
function finnish_global_render_enable_seo_field() {
$value = get_option( 'finnish_global_enable_seo_validation', true );
echo '<input type="checkbox" name="finnish_global_enable_seo_validation" value="1" ' . checked( 1, $value, false ) . ' />';
echo '<label> Enforce 55-character titles and 155-character descriptions</label>';
}
function finnish_global_render_enable_duplicate_prevention_field() {
$value = get_option( 'finnish_global_enable_duplicate_prevention', true );
echo '<input type="checkbox" name="finnish_global_enable_duplicate_prevention" value="1" ' . checked( 1, $value, false ) . ' />';
echo '<label> Prevent duplicate content across all RSS sources</label>';
}
function finnish_global_render_duplicate_action_field() {
$value = get_option( 'finnish_global_duplicate_action', 'draft' );
$actions = array(
'draft' => 'Move to Draft (with duplicate note)',
'trash' => 'Move to Trash',
'delete' => 'Delete Permanently'
);
echo '<select name="finnish_global_duplicate_action">';
foreach ( $actions as $action_id => $label ) {
echo '<option value="' . esc_attr( $action_id ) . '" ' . selected( $value, $action_id, false ) . '>' . esc_html( $label ) . '</option>';
}
echo '</select>';
echo '<p class="description">What to do when duplicate content is detected</p>';
}
function finnish_global_render_auto_publish_field() {
$value = get_option( 'finnish_global_auto_publish', false );
echo '<input type="checkbox" name="finnish_global_auto_publish" value="1" ' . checked( 1, $value, false ) . ' />';
echo '<label> Automatically publish posts with quality score ≥60%</label>';
echo '<p class="description">Posts with successful enhancements will be auto-published</p>';
}
// Inject Meta Description if present
add_action( 'wp_head', 'finnish_global_inject_meta_description', 1 );
function finnish_global_inject_meta_description() {
if ( is_single() ) {
global $post;
$meta_desc = get_post_meta( $post->ID, '_finnish_meta_description', true );
if ( ! empty( $meta_desc ) ) {
echo '<meta name="description" content="' . esc_attr( $meta_desc ) . '" />' . "\n";
// Also try to override OpenGraph if possible (though plugins might fight for this)
echo '<meta property="og:description" content="' . esc_attr( $meta_desc ) . '" />' . "\n";
}
}
}
// Initialize the plugin
run_finnish_global_automation_direct();