1085 lines
34 KiB
PHP
1085 lines
34 KiB
PHP
<?php
|
|
/**
|
|
* ==========================
|
|
* 🔐 IGNY8 FILE RULE HEADER
|
|
* ==========================
|
|
* @file : global-helpers.php
|
|
* @location : /core/admin/global-helpers.php
|
|
* @type : Function Library
|
|
* @scope : Global
|
|
* @allowed : Utility functions, data formatting, WordPress helpers
|
|
* @reusability : Globally Reusable
|
|
* @notes : Central utility functions used across all modules
|
|
*/
|
|
|
|
// Prevent direct access
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Convert cluster ID to cluster name
|
|
* Used across multiple submodules and forms
|
|
*
|
|
* @param int $cluster_id The cluster ID
|
|
* @return string The cluster name or empty string if not found
|
|
*/
|
|
function igny8_get_cluster_term_name($cluster_id) {
|
|
if (empty($cluster_id) || !is_numeric($cluster_id)) {
|
|
return '';
|
|
}
|
|
|
|
global $wpdb;
|
|
|
|
// Query the clusters table directly
|
|
$cluster_name = $wpdb->get_var($wpdb->prepare(
|
|
"SELECT cluster_name FROM {$wpdb->prefix}igny8_clusters WHERE id = %d",
|
|
$cluster_id
|
|
));
|
|
|
|
if (!empty($cluster_name)) {
|
|
return $cluster_name;
|
|
}
|
|
|
|
// If no cluster found, return empty string
|
|
return '';
|
|
}
|
|
|
|
|
|
/**
|
|
* Convert cluster name to cluster ID
|
|
* Used across multiple submodules and forms
|
|
*
|
|
* @param string $cluster_name The cluster name
|
|
* @return int The cluster ID or 0 if not found
|
|
*/
|
|
function igny8_get_cluster_id_by_name($cluster_name) {
|
|
if (empty($cluster_name) || !is_string($cluster_name)) {
|
|
return 0;
|
|
}
|
|
|
|
global $wpdb;
|
|
|
|
// Query the clusters table directly
|
|
$cluster_id = $wpdb->get_var($wpdb->prepare(
|
|
"SELECT id FROM {$wpdb->prefix}igny8_clusters WHERE cluster_name = %s",
|
|
$cluster_name
|
|
));
|
|
|
|
return $cluster_id ? (int)$cluster_id : 0;
|
|
}
|
|
|
|
/**
|
|
* Get cluster options for dropdowns
|
|
* Used across all modules that need cluster selection
|
|
*
|
|
* @return array Array of cluster options in format [['value' => id, 'label' => name], ...]
|
|
*/
|
|
function igny8_get_cluster_options() {
|
|
global $wpdb;
|
|
|
|
// Get all active clusters from database
|
|
$clusters = $wpdb->get_results(
|
|
"SELECT id, cluster_name FROM {$wpdb->prefix}igny8_clusters WHERE status = 'active' ORDER BY cluster_name ASC"
|
|
);
|
|
|
|
$options = [
|
|
['value' => '', 'label' => 'No Cluster']
|
|
];
|
|
|
|
if ($clusters) {
|
|
foreach ($clusters as $cluster) {
|
|
$options[] = [
|
|
'value' => $cluster->id,
|
|
'label' => $cluster->cluster_name
|
|
];
|
|
}
|
|
}
|
|
|
|
return $options;
|
|
}
|
|
|
|
/**
|
|
* Get standardized cluster lookup configuration for tables
|
|
* This eliminates the need to repeat lookup configuration in every table
|
|
*
|
|
* @return array Standardized cluster lookup configuration
|
|
*/
|
|
function igny8_get_cluster_lookup_config() {
|
|
return [
|
|
'type' => 'lookup',
|
|
'source_field' => 'cluster_id',
|
|
'display_field' => 'cluster_name',
|
|
'sortable' => true,
|
|
'join_query' => 'LEFT JOIN {prefix}igny8_clusters c ON {table_name}.cluster_id = c.id',
|
|
'select_field' => 'c.cluster_name as cluster_name'
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get standardized cluster lookup configuration for ideas table
|
|
* Uses keyword_cluster_id instead of cluster_id
|
|
*
|
|
* @return array Standardized cluster lookup configuration for ideas
|
|
*/
|
|
function igny8_get_ideas_cluster_lookup_config() {
|
|
return [
|
|
'type' => 'lookup',
|
|
'source_field' => 'keyword_cluster_id',
|
|
'display_field' => 'cluster_name',
|
|
'sortable' => true,
|
|
'join_query' => 'LEFT JOIN {prefix}igny8_clusters c ON {table_name}.keyword_cluster_id = c.id',
|
|
'select_field' => 'c.cluster_name as cluster_name'
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get dynamic table configuration based on AI mode
|
|
*/
|
|
function igny8_get_dynamic_table_config($table_id) {
|
|
$config = $GLOBALS['igny8_tables_config'][$table_id] ?? [];
|
|
|
|
// Apply AI-specific modifications
|
|
if ($table_id === 'writer_queue') {
|
|
$writer_mode = igny8_get_ai_setting('writer_mode', 'manual');
|
|
|
|
// Hide due_date column for AI mode
|
|
if ($writer_mode === 'ai' && isset($config['columns']['due_date'])) {
|
|
unset($config['columns']['due_date']);
|
|
|
|
// Also remove from filters
|
|
if (isset($config['filters']['due_date'])) {
|
|
unset($config['filters']['due_date']);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply status filter for drafts table
|
|
if ($table_id === 'writer_drafts') {
|
|
$config['default_filter'] = [
|
|
'status' => ['draft']
|
|
];
|
|
}
|
|
|
|
// Apply status filter for published table
|
|
if ($table_id === 'writer_published') {
|
|
$config['default_filter'] = [
|
|
'status' => ['published']
|
|
];
|
|
}
|
|
|
|
return $config;
|
|
}
|
|
|
|
/**
|
|
* Get dynamic form configuration based on table columns
|
|
*/
|
|
function igny8_get_dynamic_form_config($form_id) {
|
|
$form_config = $GLOBALS['igny8_forms_config'][$form_id] ?? [];
|
|
|
|
// For writer_queue form, conditionally include due_date field
|
|
if ($form_id === 'writer_queue') {
|
|
$writer_mode = igny8_get_ai_setting('writer_mode', 'manual');
|
|
|
|
// Remove due_date field for AI mode
|
|
if ($writer_mode === 'ai') {
|
|
$form_config['fields'] = array_filter($form_config['fields'], function($field) {
|
|
return $field['name'] !== 'due_date';
|
|
});
|
|
// Re-index array
|
|
$form_config['fields'] = array_values($form_config['fields']);
|
|
}
|
|
}
|
|
|
|
return $form_config;
|
|
}
|
|
|
|
/**
|
|
* Convert difficulty number to predefined difficulty range name
|
|
*
|
|
* @param float $difficulty The difficulty value (0-100)
|
|
* @return string The difficulty range name
|
|
*/
|
|
function igny8_get_difficulty_range_name($difficulty) {
|
|
if (empty($difficulty) || !is_numeric($difficulty)) {
|
|
return '';
|
|
}
|
|
|
|
$difficulty = floatval($difficulty);
|
|
|
|
if ($difficulty <= 20) {
|
|
return 'Very Easy';
|
|
} elseif ($difficulty <= 40) {
|
|
return 'Easy';
|
|
} elseif ($difficulty <= 60) {
|
|
return 'Medium';
|
|
} elseif ($difficulty <= 80) {
|
|
return 'Hard';
|
|
} else {
|
|
return 'Very Hard';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert difficulty text label to numeric range for database queries
|
|
*
|
|
* @param string $difficulty_label The difficulty text label
|
|
* @return array|false Array with 'min' and 'max' keys, or false if invalid
|
|
*/
|
|
function igny8_get_difficulty_numeric_range($difficulty_label) {
|
|
if (empty($difficulty_label)) {
|
|
return false;
|
|
}
|
|
|
|
switch ($difficulty_label) {
|
|
case 'Very Easy':
|
|
return ['min' => 0, 'max' => 20];
|
|
case 'Easy':
|
|
return ['min' => 21, 'max' => 40];
|
|
case 'Medium':
|
|
return ['min' => 41, 'max' => 60];
|
|
case 'Hard':
|
|
return ['min' => 61, 'max' => 80];
|
|
case 'Very Hard':
|
|
return ['min' => 81, 'max' => 100];
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get page description
|
|
*/
|
|
function igny8_get_page_description() {
|
|
$current_page = $_GET['page'] ?? '';
|
|
$sm = $_GET['sm'] ?? '';
|
|
|
|
switch ($current_page) {
|
|
case 'igny8-home':
|
|
return 'Welcome to Igny8 AI SEO OS - Your comprehensive SEO management platform.';
|
|
case 'igny8-planner':
|
|
if ($sm === 'keywords') {
|
|
return 'Manage your keywords, track search volumes, and organize by intent and difficulty.';
|
|
} elseif ($sm === 'clusters') {
|
|
return 'Group related keywords into content clusters for better topical authority.';
|
|
} elseif ($sm === 'ideas') {
|
|
return 'Generate and organize content ideas based on your keyword research.';
|
|
} elseif ($sm === 'mapping') {
|
|
return 'Map keywords and clusters to existing pages and content.';
|
|
} else {
|
|
return 'Plan and organize your content strategy with keyword research and clustering.';
|
|
}
|
|
case 'igny8-writer':
|
|
if ($sm === 'drafts') {
|
|
return 'Manage your content drafts and work in progress.';
|
|
} elseif ($sm === 'templates') {
|
|
return 'Create and manage content templates for consistent writing.';
|
|
} else {
|
|
return 'Content creation and writing tools for SEO-optimized content.';
|
|
}
|
|
case 'igny8-optimizer':
|
|
if ($sm === 'audits') {
|
|
return 'Run comprehensive SEO audits on your content and pages.';
|
|
} elseif ($sm === 'suggestions') {
|
|
return 'Get AI-powered optimization suggestions for better rankings.';
|
|
} else {
|
|
return 'SEO optimization and performance tools for better rankings.';
|
|
}
|
|
case 'igny8-linker':
|
|
if ($sm === 'backlinks') {
|
|
return 'Track and manage your backlink profile and authority.';
|
|
} elseif ($sm === 'campaigns') {
|
|
return 'Plan and execute link building campaigns effectively.';
|
|
} else {
|
|
return 'Link building and backlink management for improved authority.';
|
|
}
|
|
case 'igny8-personalize':
|
|
if ($sm === 'settings') {
|
|
return 'Configure global settings, display options, and advanced personalization settings.';
|
|
} elseif ($sm === 'content-generation') {
|
|
return 'Configure AI prompts, field detection, and content generation parameters.';
|
|
} elseif ($sm === 'rewrites') {
|
|
return 'View and manage personalized content variations and rewrites.';
|
|
} elseif ($sm === 'front-end') {
|
|
return 'Manage front-end display settings, shortcode usage, and implementation guides.';
|
|
} else {
|
|
return 'Content personalization and targeting for better engagement.';
|
|
}
|
|
case 'igny8-settings':
|
|
$sp = $_GET['sp'] ?? 'general';
|
|
if ($sp === 'status') {
|
|
return 'Monitor system health, database status, and module performance.';
|
|
} elseif ($sp === 'integration') {
|
|
return 'Configure API keys and integrate with external services.';
|
|
} elseif ($sp === 'import-export') {
|
|
return 'Import and export data, manage backups, and transfer content.';
|
|
} else {
|
|
return 'Configure plugin settings, automation, and table preferences.';
|
|
}
|
|
case 'igny8-analytics':
|
|
return 'Performance analytics and reporting for data-driven decisions.';
|
|
case 'igny8-schedules':
|
|
return 'Content scheduling and automation for consistent publishing.';
|
|
case 'igny8-help':
|
|
return 'Documentation and support resources for getting started.';
|
|
default:
|
|
return 'Igny8 AI SEO OS - Advanced SEO Management';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get page title
|
|
*/
|
|
function igny8_get_page_title() {
|
|
$current_page = $_GET['page'] ?? '';
|
|
$sm = $_GET['sm'] ?? '';
|
|
|
|
switch ($current_page) {
|
|
case 'igny8-home':
|
|
return 'Igny8 Home';
|
|
case 'igny8-planner':
|
|
if ($sm === 'keywords') {
|
|
return 'Keywords';
|
|
} elseif ($sm === 'clusters') {
|
|
return 'Clusters';
|
|
} elseif ($sm === 'ideas') {
|
|
return 'Ideas';
|
|
} elseif ($sm === 'mapping') {
|
|
return 'Mapping';
|
|
} else {
|
|
return 'Planner';
|
|
}
|
|
case 'igny8-writer':
|
|
if ($sm === 'tasks') {
|
|
return 'Content Queue / Tasks';
|
|
} elseif ($sm === 'drafts') {
|
|
return 'Content Generated';
|
|
} elseif ($sm === 'published') {
|
|
return 'Live Content';
|
|
} else {
|
|
return 'Content Writer';
|
|
}
|
|
case 'igny8-optimizer':
|
|
if ($sm === 'audits') {
|
|
return 'Audits';
|
|
} elseif ($sm === 'suggestions') {
|
|
return 'Suggestions';
|
|
} else {
|
|
return 'Optimizer';
|
|
}
|
|
case 'igny8-linker':
|
|
if ($sm === 'backlinks') {
|
|
return 'Backlinks';
|
|
} elseif ($sm === 'campaigns') {
|
|
return 'Campaigns';
|
|
} else {
|
|
return 'Linker';
|
|
}
|
|
case 'igny8-personalize':
|
|
if ($sm === 'settings') {
|
|
return 'Personalization Settings';
|
|
} elseif ($sm === 'content-generation') {
|
|
return 'Content Generation';
|
|
} elseif ($sm === 'rewrites') {
|
|
return 'Rewrites';
|
|
} elseif ($sm === 'front-end') {
|
|
return 'Front-end';
|
|
} else {
|
|
return 'Personalize';
|
|
}
|
|
case 'igny8-thinker':
|
|
$sp = $_GET['sp'] ?? 'main';
|
|
if ($sp === 'main') {
|
|
return 'AI Thinker Dashboard';
|
|
} elseif ($sp === 'prompts') {
|
|
return 'AI Prompts';
|
|
} elseif ($sp === 'profile') {
|
|
return 'AI Profile';
|
|
} elseif ($sp === 'strategies') {
|
|
return 'Content Strategies';
|
|
} elseif ($sp === 'image-testing') {
|
|
return 'AI Image Testing';
|
|
} else {
|
|
return 'AI Thinker';
|
|
}
|
|
case 'igny8-settings':
|
|
$sp = $_GET['sp'] ?? 'general';
|
|
if ($sp === 'status') {
|
|
return 'System Status';
|
|
} elseif ($sp === 'integration') {
|
|
return 'API Integration';
|
|
} elseif ($sp === 'import-export') {
|
|
return 'Import/Export';
|
|
} else {
|
|
return 'General Settings';
|
|
}
|
|
case 'igny8-analytics':
|
|
return 'Analytics';
|
|
case 'igny8-schedules':
|
|
return 'Schedules';
|
|
case 'igny8-test':
|
|
$sp = $_GET['sp'] ?? 'system-testing';
|
|
if ($sp === 'system-testing') {
|
|
return 'System Testing';
|
|
} elseif ($sp === 'temp-function-testing') {
|
|
return 'Function Testing';
|
|
} else {
|
|
return 'Test Page';
|
|
}
|
|
case 'igny8-help':
|
|
return 'Help';
|
|
default:
|
|
return 'Igny8 AI SEO OS';
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// REMOVED: Task variations functionality - tasks don't need variations
|
|
|
|
/**
|
|
* Safe logging helper for Igny8 operations
|
|
*
|
|
* @param string $type Log type (e.g., 'queue_to_writer', 'task_created')
|
|
* @param mixed $data Log data (string, array, or object)
|
|
*/
|
|
function igny8_write_log($type, $data) {
|
|
global $wpdb;
|
|
|
|
try {
|
|
$wpdb->insert($wpdb->prefix . 'igny8_logs', [
|
|
'level' => 'info',
|
|
'message' => sanitize_text_field($type),
|
|
'context' => is_array($data) ? wp_json_encode($data) : (is_string($data) ? $data : null),
|
|
'source' => 'igny8_plugin',
|
|
'user_id' => get_current_user_id(),
|
|
], ['%s', '%s', '%s', '%s', '%d']);
|
|
} catch (Exception $e) {
|
|
// Log to error log if database logging fails
|
|
error_log('Igny8: Failed to write to logs table: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get clusters for select dropdown
|
|
*
|
|
* @return array Array of cluster options for select dropdown
|
|
*/
|
|
function igny8_get_clusters_for_select() {
|
|
global $wpdb;
|
|
|
|
$clusters = $wpdb->get_results(
|
|
"SELECT id, cluster_name FROM {$wpdb->prefix}igny8_clusters ORDER BY cluster_name ASC"
|
|
);
|
|
|
|
$options = ['' => 'Select Cluster'];
|
|
foreach ($clusters as $cluster) {
|
|
$options[$cluster->id] = $cluster->cluster_name;
|
|
}
|
|
|
|
return $options;
|
|
}
|
|
|
|
/**
|
|
* Get sector options for dropdowns
|
|
* Used across all modules that need sector selection
|
|
* Only returns sectors from saved planner settings
|
|
*
|
|
* @return array Array of sector options in format [['value' => id, 'label' => name], ...]
|
|
*/
|
|
function igny8_get_sector_options() {
|
|
// Get saved sector selection from user meta
|
|
$user_id = get_current_user_id();
|
|
$saved_selection = get_user_meta($user_id, 'igny8_planner_sector_selection', true);
|
|
|
|
$options = [];
|
|
|
|
// If no saved selection, return empty options
|
|
if (empty($saved_selection)) {
|
|
return $options;
|
|
}
|
|
|
|
// Add only child sectors (not parent)
|
|
if (isset($saved_selection['children']) && is_array($saved_selection['children'])) {
|
|
foreach ($saved_selection['children'] as $child) {
|
|
if (isset($child['id']) && isset($child['name'])) {
|
|
$options[] = [
|
|
'value' => $child['id'],
|
|
'label' => $child['name']
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $options;
|
|
}
|
|
|
|
/**
|
|
* Get sectors for select dropdown
|
|
* Only returns sectors from saved planner settings
|
|
*
|
|
* @return array Array of sector options for select dropdown
|
|
*/
|
|
function igny8_get_sectors_for_select() {
|
|
// Get saved sector selection from user meta
|
|
$user_id = get_current_user_id();
|
|
$saved_selection = get_user_meta($user_id, 'igny8_planner_sector_selection', true);
|
|
|
|
$options = [];
|
|
|
|
// If no saved selection, return empty options
|
|
if (empty($saved_selection)) {
|
|
return $options;
|
|
}
|
|
|
|
// Add only child sectors (not parent)
|
|
if (isset($saved_selection['children']) && is_array($saved_selection['children'])) {
|
|
foreach ($saved_selection['children'] as $child) {
|
|
if (isset($child['id']) && isset($child['name'])) {
|
|
$options[$child['id']] = $child['name'];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $options;
|
|
}
|
|
|
|
/**
|
|
* Get ideas for select dropdown
|
|
*
|
|
* @return array Array of idea options for select dropdown
|
|
*/
|
|
function igny8_get_ideas_for_select() {
|
|
global $wpdb;
|
|
|
|
$ideas = $wpdb->get_results(
|
|
"SELECT id, idea_title FROM {$wpdb->prefix}igny8_content_ideas ORDER BY idea_title ASC"
|
|
);
|
|
|
|
$options = ['' => 'Select Idea'];
|
|
foreach ($ideas as $idea) {
|
|
$options[$idea->id] = $idea->idea_title;
|
|
}
|
|
|
|
return $options;
|
|
}
|
|
|
|
/**
|
|
* Transform database field names to human-readable labels
|
|
*
|
|
* @param string $field_name The database field name (snake_case)
|
|
* @return string The human-readable label (Title Case)
|
|
*/
|
|
function igny8_humanize_label($field) {
|
|
// Handle non-string input safely
|
|
if (!is_string($field)) {
|
|
return '';
|
|
}
|
|
|
|
// Remove any potentially dangerous characters
|
|
$field = sanitize_key($field);
|
|
|
|
// Specific field mappings for better readability
|
|
$field_mappings = [
|
|
// Clusters table
|
|
'cluster_name' => 'Cluster Name',
|
|
'sector_id' => 'Sectors',
|
|
'keyword_count' => 'Keywords',
|
|
'total_volume' => 'Total Volume',
|
|
'avg_difficulty' => 'Avg KD',
|
|
'mapped_pages_count' => 'Mapped Pages',
|
|
'created_at' => 'Created',
|
|
'updated_at' => 'Updated',
|
|
|
|
// Keywords table
|
|
'search_volume' => 'Volume',
|
|
'difficulty' => 'Difficulty',
|
|
'cpc' => 'CPC',
|
|
'intent' => 'Intent',
|
|
'status' => 'Status',
|
|
'cluster_id' => 'Cluster',
|
|
|
|
// Ideas table
|
|
'idea_title' => 'Title',
|
|
'idea_description' => 'Description',
|
|
'content_structure' => 'Structure',
|
|
'content_angle' => 'Content Angle',
|
|
'keyword_cluster_id' => 'Cluster',
|
|
'estimated_word_count' => 'Words',
|
|
'ai_generated' => 'AI Generated',
|
|
'mapped_post_id' => 'Mapped Post',
|
|
'tasks_count' => 'Tasks Count',
|
|
|
|
// Tasks table
|
|
'content_type' => 'Content Type',
|
|
'cluster_id' => 'Cluster',
|
|
'keywords' => 'Keywords',
|
|
'schedule_at' => 'Scheduled',
|
|
'assigned_post_id' => 'Assigned Post',
|
|
'created_at' => 'Created',
|
|
'updated_at' => 'Updated',
|
|
|
|
// Campaigns table
|
|
'backlink_count' => 'Backlinks',
|
|
'live_links_count' => 'Live Links',
|
|
|
|
// Mapping table
|
|
'page_id' => 'Page ID',
|
|
'coverage_percentage' => 'Coverage %',
|
|
'coverage_status' => 'Status',
|
|
'last_updated' => 'Updated'
|
|
];
|
|
|
|
// Check for specific mapping first
|
|
if (isset($field_mappings[$field])) {
|
|
return $field_mappings[$field];
|
|
}
|
|
|
|
// Fallback: convert snake_case to Title Case
|
|
return ucwords(str_replace('_', ' ', $field));
|
|
}
|
|
|
|
/**
|
|
* Transform database field names to human-readable labels (legacy function)
|
|
* @deprecated Use igny8_humanize_label() instead
|
|
*/
|
|
function igny8_transform_field_label($field_name) {
|
|
return igny8_humanize_label($field_name);
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Map cluster to keywords
|
|
*
|
|
* @param int $cluster_id Cluster ID to map keywords to
|
|
* @param array $keyword_ids Array of keyword IDs to map
|
|
* @return array ['success' => bool, 'message' => string, 'mapped_count' => int]
|
|
*/
|
|
function igny8_map_cluster_to_keywords($cluster_id, $keyword_ids) {
|
|
global $wpdb;
|
|
|
|
if (empty($cluster_id) || !is_numeric($cluster_id)) {
|
|
return ['success' => false, 'message' => 'Invalid cluster ID provided', 'mapped_count' => 0];
|
|
}
|
|
|
|
if (empty($keyword_ids) || !is_array($keyword_ids)) {
|
|
return ['success' => false, 'message' => 'No keywords provided for mapping', 'mapped_count' => 0];
|
|
}
|
|
|
|
$cluster_id = intval($cluster_id);
|
|
|
|
// Verify cluster exists
|
|
$cluster_exists = $wpdb->get_var($wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$wpdb->prefix}igny8_clusters WHERE id = %d",
|
|
$cluster_id
|
|
));
|
|
|
|
if (!$cluster_exists) {
|
|
return ['success' => false, 'message' => 'Cluster not found', 'mapped_count' => 0];
|
|
}
|
|
|
|
// Sanitize and validate keyword IDs
|
|
$keyword_ids = array_map('intval', $keyword_ids);
|
|
$keyword_ids = array_filter($keyword_ids, function($id) { return $id > 0; });
|
|
|
|
if (empty($keyword_ids)) {
|
|
return ['success' => false, 'message' => 'No valid keyword IDs provided', 'mapped_count' => 0];
|
|
}
|
|
|
|
// Get old cluster IDs for metrics update
|
|
$placeholders = implode(',', array_fill(0, count($keyword_ids), '%d'));
|
|
$old_cluster_ids = $wpdb->get_col($wpdb->prepare(
|
|
"SELECT DISTINCT cluster_id FROM {$wpdb->prefix}igny8_keywords WHERE id IN ({$placeholders}) AND cluster_id IS NOT NULL",
|
|
$keyword_ids
|
|
));
|
|
|
|
// Update keywords to new cluster
|
|
$mapped_count = $wpdb->query($wpdb->prepare(
|
|
"UPDATE {$wpdb->prefix}igny8_keywords SET cluster_id = %d, status = 'mapped', updated_at = CURRENT_TIMESTAMP WHERE id IN ({$placeholders})",
|
|
array_merge([$cluster_id], $keyword_ids)
|
|
));
|
|
|
|
if ($mapped_count === false) {
|
|
return ['success' => false, 'message' => 'Failed to map keywords to cluster', 'mapped_count' => 0];
|
|
}
|
|
|
|
// Update metrics for old clusters (they lost keywords)
|
|
if (!empty($old_cluster_ids)) {
|
|
foreach (array_unique($old_cluster_ids) as $old_cluster_id) {
|
|
if ($old_cluster_id && $old_cluster_id != $cluster_id) {
|
|
igny8_update_cluster_metrics($old_cluster_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update metrics for new cluster (gained keywords)
|
|
igny8_update_cluster_metrics($cluster_id);
|
|
|
|
return [
|
|
'success' => true,
|
|
'message' => "Successfully mapped {$mapped_count} keyword(s) to cluster",
|
|
'mapped_count' => $mapped_count
|
|
];
|
|
}
|
|
|
|
/**
|
|
* =============================================
|
|
* UNIFIED DATA VALIDATION LAYER
|
|
* =============================================
|
|
*/
|
|
|
|
/**
|
|
* Unified record validation function for all Planner module tables
|
|
*
|
|
* @param string $table_id Table ID (e.g., 'planner_keywords', 'planner_clusters')
|
|
* @param array $data Array of field data to validate
|
|
* @return array ['valid' => bool, 'error' => string|null]
|
|
*/
|
|
|
|
/**
|
|
* Check if automation is enabled for the current user
|
|
*
|
|
* @return bool True if automation is enabled
|
|
*/
|
|
function igny8_is_automation_enabled() {
|
|
// Default ON for admin users, OFF for others
|
|
if (current_user_can('manage_options')) {
|
|
return true;
|
|
}
|
|
|
|
// Check if user has specific automation capability
|
|
return current_user_can('edit_posts');
|
|
}
|
|
|
|
/**
|
|
* Helper function to get sector name
|
|
* Used for table display to show sector names instead of IDs
|
|
*
|
|
* @param mixed $sector_id The sector ID
|
|
* @return string The sector name or empty string if not found or 0/null
|
|
*/
|
|
function igny8_get_sector_name($sector_id) {
|
|
// Return empty string for 0, null, or empty values
|
|
if (empty($sector_id) || !is_numeric($sector_id) || intval($sector_id) == 0) {
|
|
return '';
|
|
}
|
|
|
|
// Check if sectors taxonomy exists
|
|
if (!taxonomy_exists('sectors')) {
|
|
return '';
|
|
}
|
|
|
|
// Try to get from WordPress taxonomy
|
|
$term = get_term(intval($sector_id), 'sectors');
|
|
if ($term && !is_wp_error($term)) {
|
|
return $term->name;
|
|
}
|
|
|
|
// If term not found, return empty string instead of fallback
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Generate cluster name from keywords
|
|
*
|
|
* @param array $keywords Array of keyword objects
|
|
* @return string Generated cluster name
|
|
*/
|
|
function igny8_generate_cluster_name_from_keywords($keywords) {
|
|
if (empty($keywords)) {
|
|
return 'Auto-Generated Cluster';
|
|
}
|
|
|
|
// Get the most common words from keywords
|
|
$all_words = [];
|
|
foreach ($keywords as $keyword) {
|
|
$words = explode(' ', strtolower($keyword->keyword));
|
|
$all_words = array_merge($all_words, $words);
|
|
}
|
|
|
|
// Count word frequency
|
|
$word_count = array_count_values($all_words);
|
|
arsort($word_count);
|
|
|
|
// Get top 2-3 most common words
|
|
$top_words = array_slice(array_keys($word_count), 0, 3);
|
|
|
|
if (empty($top_words)) {
|
|
return 'Auto-Generated Cluster';
|
|
}
|
|
|
|
return ucwords(implode(' ', $top_words)) . ' Cluster';
|
|
}
|
|
|
|
/**
|
|
* Calculate relevance score for mapping suggestions
|
|
*
|
|
* @param string $term The search term
|
|
* @param string $title The post title
|
|
* @param string $slug The post slug
|
|
* @return int Relevance score (0-100)
|
|
*/
|
|
function igny8_calculate_relevance_score($term, $title, $slug) {
|
|
$score = 0;
|
|
$term_lower = strtolower($term);
|
|
$title_lower = strtolower($title);
|
|
$slug_lower = strtolower($slug);
|
|
|
|
// Exact match in title
|
|
if (strpos($title_lower, $term_lower) !== false) {
|
|
$score += 50;
|
|
}
|
|
|
|
// Exact match in slug
|
|
if (strpos($slug_lower, $term_lower) !== false) {
|
|
$score += 30;
|
|
}
|
|
|
|
// Word match in title
|
|
$title_words = explode(' ', $title_lower);
|
|
$term_words = explode(' ', $term_lower);
|
|
foreach ($term_words as $word) {
|
|
if (in_array($word, $title_words)) {
|
|
$score += 10;
|
|
}
|
|
}
|
|
|
|
return min($score, 100);
|
|
}
|
|
|
|
/**
|
|
* Remove duplicate suggestions from mapping suggestions
|
|
*
|
|
* @param array $suggestions Array of suggestion objects
|
|
* @return array Deduplicated suggestions
|
|
*/
|
|
function igny8_remove_duplicate_suggestions($suggestions) {
|
|
$seen = [];
|
|
$unique = [];
|
|
|
|
foreach ($suggestions as $suggestion) {
|
|
$key = $suggestion['page_id'];
|
|
if (!isset($seen[$key])) {
|
|
$seen[$key] = true;
|
|
$unique[] = $suggestion;
|
|
}
|
|
}
|
|
|
|
return $unique;
|
|
}
|
|
|
|
/**
|
|
* Get Import/Export Configuration for a table
|
|
*
|
|
* @param string $table_id The table ID (e.g., 'planner_keywords')
|
|
* @return array|false Configuration array or false if not found
|
|
*/
|
|
function igny8_get_import_export_config($table_id) {
|
|
static $config_cache = null;
|
|
|
|
if ($config_cache === null) {
|
|
$config_path = plugin_dir_path(dirname(__FILE__)) . '../modules/config/import-export-config.php';
|
|
if (file_exists($config_path)) {
|
|
$config_cache = include $config_path;
|
|
} else {
|
|
$config_cache = [];
|
|
}
|
|
}
|
|
|
|
return isset($config_cache[$table_id]) ? $config_cache[$table_id] : false;
|
|
}
|
|
|
|
|
|
/**
|
|
* AJAX handler for testing API connection
|
|
*/
|
|
function igny8_test_connection_ajax() {
|
|
try {
|
|
// Check if user has permission
|
|
if (!current_user_can('manage_options')) {
|
|
wp_send_json_error('Insufficient permissions');
|
|
return;
|
|
}
|
|
|
|
// Verify nonce
|
|
if (!check_ajax_referer('igny8_test_connection', 'nonce', false)) {
|
|
wp_send_json_error('Invalid nonce');
|
|
return;
|
|
}
|
|
|
|
// Test basic HTTP functionality
|
|
$test_url = 'https://httpbin.org/get';
|
|
$response = wp_remote_get($test_url, ['timeout' => 10]);
|
|
|
|
if (is_wp_error($response)) {
|
|
wp_send_json_error('HTTP request failed: ' . $response->get_error_message());
|
|
return;
|
|
}
|
|
|
|
$response_code = wp_remote_retrieve_response_code($response);
|
|
if ($response_code !== 200) {
|
|
wp_send_json_error('HTTP request returned status code: ' . $response_code);
|
|
return;
|
|
}
|
|
|
|
wp_send_json_success('Connection test passed');
|
|
|
|
} catch (Exception $e) {
|
|
wp_send_json_error('Exception: ' . $e->getMessage());
|
|
}
|
|
}
|
|
add_action('wp_ajax_igny8_test_connection', 'igny8_test_connection_ajax');
|
|
|
|
/**
|
|
* AJAX handler for testing API with response
|
|
*/
|
|
function igny8_test_api_ajax() {
|
|
try {
|
|
// Check if user has permission
|
|
if (!current_user_can('manage_options')) {
|
|
wp_send_json_error('Insufficient permissions');
|
|
return;
|
|
}
|
|
|
|
// Verify nonce
|
|
if (!check_ajax_referer('igny8_ajax_nonce', 'nonce', false)) {
|
|
wp_send_json_error('Invalid nonce');
|
|
return;
|
|
}
|
|
|
|
// Get API key
|
|
$api_key = get_option('igny8_api_key', '');
|
|
if (empty($api_key)) {
|
|
wp_send_json_error('API key not configured');
|
|
return;
|
|
}
|
|
|
|
// Get parameters
|
|
$with_response = isset($_POST['with_response']) ? (bool) $_POST['with_response'] : false;
|
|
|
|
// Test API connection
|
|
$result = igny8_test_connection($api_key, $with_response);
|
|
|
|
if (is_wp_error($result)) {
|
|
wp_send_json_error($result->get_error_message());
|
|
return;
|
|
}
|
|
|
|
if (is_array($result) && isset($result['success'])) {
|
|
if ($result['success']) {
|
|
wp_send_json_success($result);
|
|
} else {
|
|
wp_send_json_error($result['message'] ?? 'API test failed');
|
|
}
|
|
} else {
|
|
wp_send_json_success(['message' => 'API connection successful', 'response' => $result]);
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
wp_send_json_error('Exception: ' . $e->getMessage());
|
|
}
|
|
}
|
|
add_action('wp_ajax_igny8_test_api', 'igny8_test_api_ajax');
|
|
|
|
/**
|
|
* Get saved sector selection from user meta
|
|
*
|
|
* @return array|false Saved sector selection data or false if not set
|
|
*/
|
|
function igny8_get_saved_sector_selection() {
|
|
$user_id = get_current_user_id();
|
|
$saved_selection = get_user_meta($user_id, 'igny8_planner_sector_selection', true);
|
|
return !empty($saved_selection) ? $saved_selection : false;
|
|
}
|
|
|
|
/**
|
|
* Get system-wide AI workflow data for main dashboard
|
|
*
|
|
* @return array Array of step data with status and counts for all 9 workflow steps
|
|
*/
|
|
function igny8_get_system_workflow_data() {
|
|
global $wpdb;
|
|
|
|
// Get counts for each step
|
|
$keywords_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_keywords");
|
|
$unmapped_keywords = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_keywords WHERE cluster_id IS NULL OR cluster_id = 0");
|
|
$clusters_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_clusters");
|
|
$ideas_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_content_ideas");
|
|
$queued_ideas = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_content_ideas WHERE status = 'new'");
|
|
$queued_tasks = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_tasks WHERE status IN ('pending', 'queued', 'new')");
|
|
$draft_tasks = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_tasks WHERE status IN ('draft', 'in_progress', 'review')");
|
|
$published_tasks = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_tasks WHERE status = 'completed'");
|
|
|
|
// Check sector selection
|
|
$sector_selected = !empty(igny8_get_saved_sector_selection());
|
|
|
|
// Check if modules are enabled
|
|
$planner_enabled = igny8_is_module_enabled('planner');
|
|
$writer_enabled = igny8_is_module_enabled('writer');
|
|
|
|
return [
|
|
'keywords' => [
|
|
'count' => $keywords_count,
|
|
'unmapped' => $unmapped_keywords,
|
|
'status' => $keywords_count > 0 ? 'completed' : 'missing',
|
|
'module_enabled' => $planner_enabled,
|
|
'url' => $planner_enabled ? '?page=igny8-planner&sm=keywords' : null
|
|
],
|
|
'sector' => [
|
|
'selected' => $sector_selected,
|
|
'status' => $sector_selected ? 'completed' : 'missing',
|
|
'module_enabled' => $planner_enabled,
|
|
'url' => $planner_enabled ? '?page=igny8-planner' : null
|
|
],
|
|
'clusters' => [
|
|
'count' => $clusters_count,
|
|
'unmapped_keywords' => $unmapped_keywords,
|
|
'status' => $unmapped_keywords == 0 && $clusters_count > 0 ? 'completed' : ($unmapped_keywords > 0 ? 'in_progress' : 'missing'),
|
|
'module_enabled' => $planner_enabled,
|
|
'url' => $planner_enabled ? '?page=igny8-planner&sm=clusters' : null
|
|
],
|
|
'ideas' => [
|
|
'count' => $ideas_count,
|
|
'status' => $ideas_count > 0 ? 'completed' : 'missing',
|
|
'module_enabled' => $planner_enabled,
|
|
'url' => $planner_enabled ? '?page=igny8-planner&sm=ideas' : null
|
|
],
|
|
'queue' => [
|
|
'queued_ideas' => $queued_ideas,
|
|
'status' => $queued_ideas == 0 && $ideas_count > 0 ? 'completed' : ($queued_ideas > 0 ? 'in_progress' : 'missing'),
|
|
'module_enabled' => $planner_enabled,
|
|
'url' => $planner_enabled ? '?page=igny8-planner&sm=ideas' : null
|
|
],
|
|
'drafts' => [
|
|
'queued_tasks' => $queued_tasks,
|
|
'draft_tasks' => $draft_tasks,
|
|
'status' => $queued_tasks > 0 ? 'in_progress' : ($draft_tasks > 0 ? 'completed' : 'missing'),
|
|
'module_enabled' => $writer_enabled,
|
|
'url' => $writer_enabled ? '?page=igny8-writer&sm=tasks' : null
|
|
],
|
|
'publish' => [
|
|
'published_tasks' => $published_tasks,
|
|
'draft_tasks' => $draft_tasks,
|
|
'status' => $published_tasks > 0 ? 'completed' : ($draft_tasks > 0 ? 'in_progress' : 'missing'),
|
|
'module_enabled' => $writer_enabled,
|
|
'url' => $writer_enabled ? '?page=igny8-writer&sm=drafts' : null
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Display image prompts in a formatted way for table display
|
|
*
|
|
* @param string $image_prompts JSON string of image prompts
|
|
* @return string Formatted display string
|
|
*/
|
|
|
|
|