[
'queued' => 'Queued',
'in_progress' => 'In Progress',
'draft' => 'Draft',
'review' => 'Review',
'published' => 'Published'
],
'priority' => [
'low' => 'Low',
'medium' => 'Medium',
'high' => 'High',
'urgent' => 'Urgent'
],
'content_type' => [
'blog_post' => 'Blog Post',
'landing_page' => 'Landing Page',
'product_page' => 'Product Page',
'guide_tutorial' => 'Guide Tutorial',
'news_article' => 'News Article',
'review' => 'Review',
'comparison' => 'Comparison',
'email' => 'Email',
'social_media' => 'Social Media'
]
];
// Check for specific field mapping first
if (isset($field_mappings[$field_name][$value])) {
return $field_mappings[$field_name][$value];
}
// Fallback: convert snake_case to Title Case
return ucwords(str_replace('_', ' ', $value));
}
/**
* Get proper case for enum values
*
* @param string $value The snake_case value
* @return string The proper case value
*/
function igny8_get_proper_case_enum($value) {
$enum_mapping = [
// Content Type values
'blog_post' => 'Blog Post',
'landing_page' => 'Landing Page',
'product_page' => 'Product Page',
'guide_tutorial' => 'Guide Tutorial',
'news_article' => 'News Article',
'review' => 'Review',
'comparison' => 'Comparison',
'page' => 'Page',
'product' => 'Product',
'product_description' => 'Product Description',
'email' => 'Email',
'social_media' => 'Social Media',
// Status values
'unmapped' => 'Unmapped',
'mapped' => 'Mapped',
'queued' => 'Queued',
'published' => 'Published',
'active' => 'Active',
'inactive' => 'Inactive',
'archived' => 'Archived',
'draft' => 'Draft',
'in_progress' => 'In Progress',
'completed' => 'Completed',
'cancelled' => 'Cancelled',
'pending' => 'Pending',
'failed' => 'Failed',
'lost' => 'Lost',
'approved' => 'Approved',
'rejected' => 'Rejected',
'needs_revision' => 'Needs Revision',
'planning' => 'Planning',
'paused' => 'Paused',
'review' => 'Review',
// Intent values
'informational' => 'Informational',
'navigational' => 'Navigational',
'transactional' => 'Transactional',
'commercial' => 'Commercial',
// Link type values
'dofollow' => 'Dofollow',
'nofollow' => 'Nofollow',
'sponsored' => 'Sponsored',
'ugc' => 'UGC',
// Coverage status values
'fully_mapped' => 'Fully Mapped',
'partially_mapped' => 'Partially Mapped',
'not_mapped' => 'Not Mapped',
// Suggestion type values
'title_optimization' => 'Title Optimization',
'meta_description' => 'Meta Description',
'heading_structure' => 'Heading Structure',
'content_improvement' => 'Content Improvement',
'internal_linking' => 'Internal Linking',
// Tone values
'professional' => 'Professional',
'casual' => 'Casual',
'friendly' => 'Friendly',
'authoritative' => 'Authoritative',
'conversational' => 'Conversational',
// Category values
'business' => 'Business',
'creative' => 'Creative',
'technical' => 'Technical',
'marketing' => 'Marketing',
'educational' => 'Educational'
];
return $enum_mapping[$value] ?? ucwords(str_replace('_', ' ', $value));
}
/**
* Apply proper case transformation to a value
*
* @param string $value The value to transform
* @param string $column_name The column name for special handling
* @return string The transformed value
*/
function igny8_apply_proper_case($value, $column_name) {
// Apply global proper case transformation to all enum fields
return igny8_get_proper_case_enum($value);
}
/**
* Format date for created_at column (tasks table)
* Shows hours/days ago if less than 30 days, month/day if greater
*/
function igny8_format_created_date($date_string) {
if (empty($date_string)) {
return 'Never';
}
try {
// Use WordPress timezone
$wp_timezone = wp_timezone();
$date = new DateTime($date_string, $wp_timezone);
$now = new DateTime('now', $wp_timezone);
$diff = $now->diff($date);
// Calculate total days difference
$total_days = $diff->days;
// If less than 30 days, show relative time
if ($total_days < 30) {
if ($total_days == 0) {
if ($diff->h > 0) {
$result = $diff->h . ' hour' . ($diff->h > 1 ? 's' : '') . ' ago';
} elseif ($diff->i > 0) {
$result = $diff->i . ' minute' . ($diff->i > 1 ? 's' : '') . ' ago';
} else {
$result = 'Just now';
}
} else {
$result = $total_days . ' day' . ($total_days > 1 ? 's' : '') . ' ago';
}
} else {
// If 30+ days, show month and day
$result = $date->format('M j');
}
return $result;
} catch (Exception $e) {
// Fallback to original date if parsing fails
$fallback = wp_date('M j', strtotime($date_string));
return $fallback;
}
}
/**
* Format date for updated_at column (drafts/published tables)
* Shows hours/days ago if less than 30 days, month/day if greater
*/
function igny8_format_updated_date($date_string) {
if (empty($date_string)) {
return 'Never';
}
try {
// Use WordPress timezone
$wp_timezone = wp_timezone();
$date = new DateTime($date_string, $wp_timezone);
$now = new DateTime('now', $wp_timezone);
$diff = $now->diff($date);
// Calculate total days difference
$total_days = $diff->days;
// If less than 30 days, show relative time
if ($total_days < 30) {
if ($total_days == 0) {
if ($diff->h > 0) {
return $diff->h . ' hour' . ($diff->h > 1 ? 's' : '') . ' ago';
} elseif ($diff->i > 0) {
return $diff->i . ' minute' . ($diff->i > 1 ? 's' : '') . ' ago';
} else {
return 'Just now';
}
} else {
return $total_days . ' day' . ($total_days > 1 ? 's' : '') . ' ago';
}
} else {
// If 30+ days, show month and day
return $date->format('M j');
}
} catch (Exception $e) {
// Fallback to original date if parsing fails
return wp_date('M j', strtotime($date_string));
}
}
/**
* Display image prompts in a formatted way for table display
*
* @param string $image_prompts JSON string of image prompts
* @return string Formatted display string
*/
function igny8_display_image_prompts($image_prompts) {
if (empty($image_prompts)) {
return 'No prompts';
}
// Try to decode JSON
$prompts = json_decode($image_prompts, true);
if (!$prompts || !is_array($prompts)) {
return 'Invalid format';
}
$output = '
';
$count = 0;
foreach ($prompts as $key => $prompt) {
if (!empty($prompt)) {
$count++;
$label = ucfirst(str_replace('_', ' ', $key));
$truncated = strlen($prompt) > 50 ? substr($prompt, 0, 50) . '...' : $prompt;
$output .= '
';
$output .= '' . esc_html($label) . ': ';
$output .= '' . esc_html($truncated) . '';
$output .= '
';
}
}
if ($count === 0) {
$output = '
No prompts';
} else {
$output .= '
';
}
return $output;
}
/**
* Format structured description for display
*/
function igny8_format_structured_description_for_display($structured_description) {
if (!is_array($structured_description)) {
return 'No structured outline available';
}
$formatted = "";
$formatted .= "
Content Outline
";
// Handle introduction section with hook
if (!empty($structured_description['introduction'])) {
$formatted .= "
";
// Add hook if it exists
if (!empty($structured_description['introduction']['hook'])) {
$formatted .= "
Hook: " . esc_html($structured_description['introduction']['hook']) . "
";
}
// Add paragraphs if they exist
if (!empty($structured_description['introduction']['paragraphs']) && is_array($structured_description['introduction']['paragraphs'])) {
$paragraph_count = 1;
foreach ($structured_description['introduction']['paragraphs'] as $paragraph) {
if (!empty($paragraph['details'])) {
$formatted .= "
Intro Paragraph " . $paragraph_count . ": " . esc_html($paragraph['details']) . "
";
$paragraph_count++;
}
}
}
$formatted .= "
";
}
// Handle H2 sections if they exist
if (!empty($structured_description['H2']) && is_array($structured_description['H2'])) {
foreach ($structured_description['H2'] as $h2_section) {
$formatted .= "
";
$formatted .= "
" . esc_html($h2_section['heading']) . "
";
if (!empty($h2_section['subsections'])) {
$formatted .= "
";
foreach ($h2_section['subsections'] as $h3_section) {
$formatted .= "- ";
$formatted .= "" . esc_html($h3_section['subheading']) . "";
$formatted .= " (" . esc_html($h3_section['content_type']) . ")";
$formatted .= "
" . esc_html($h3_section['details']) . "";
$formatted .= " ";
}
$formatted .= "
";
}
$formatted .= "
";
}
}
$formatted .= "
";
return $formatted;
}
/**
* Fetch real table data from database
* Phase-2: Real Data Loading from Config
*
* @param string $tableId The table identifier
* @param array $filters Optional filters to apply
* @param int $page Page number for pagination
* @return array Real data structure
*/
function igny8_fetch_table_data($tableId, $filters = [], $page = 1, $per_page = null) {
global $wpdb;
// Sanitize all inputs
$tableId = sanitize_text_field($tableId);
$page = intval($page);
$per_page = $per_page ? intval($per_page) : get_option('igny8_records_per_page', 20);
// Sanitize filters array
if (is_array($filters)) {
$filters = array_map('sanitize_text_field', $filters);
} else {
$filters = [];
}
// Get table configuration to apply default filters
$tables_config = require plugin_dir_path(__FILE__) . '../config/tables-config.php';
$GLOBALS['igny8_tables_config'] = $tables_config;
$table_config = igny8_get_dynamic_table_config($tableId);
// Apply default filters - merge with existing filters
if (isset($table_config['default_filter'])) {
$default_filters = $table_config['default_filter'];
foreach ($default_filters as $key => $value) {
// Only apply default filter if not already set by user
if (!isset($filters[$key]) || empty($filters[$key])) {
$filters[$key] = $value;
}
}
}
// Force completed status filter for writer_published table
if ($tableId === 'writer_published') {
$filters['status'] = ['completed'];
}
// Get table name from table ID
$table_name = igny8_get_table_name($tableId);
// Check if table exists
if (!igny8_table_exists($table_name)) {
// Return empty data if table doesn't exist
return [
'rows' => [],
'pagination' => [
'page' => $page,
'total' => 0,
'per_page' => $per_page,
'total_pages' => 0
]
];
}
// Build WHERE clause for filters
$where_conditions = [];
$where_values = [];
if (!empty($filters)) {
foreach ($filters as $key => $value) {
if (!empty($value)) {
// Check if this is a range filter (min/max)
if (strpos($key, '-min') !== false) {
$field_name = str_replace('-min', '', $key);
$where_conditions[] = "`{$field_name}` >= %d";
$where_values[] = intval($value);
} elseif (strpos($key, '-max') !== false) {
$field_name = str_replace('-max', '', $key);
$where_conditions[] = "`{$field_name}` <= %d";
$where_values[] = intval($value);
} elseif (in_array($key, ['status', 'intent'])) {
// For dropdown filters (status, intent), handle both single values and arrays
if (is_array($value)) {
// For array values (like ['draft'] from default filters)
$placeholders = implode(',', array_fill(0, count($value), '%s'));
$where_conditions[] = "`{$key}` IN ({$placeholders})";
$where_values = array_merge($where_values, $value);
} else {
// For single values
$where_conditions[] = "`{$key}` = %s";
$where_values[] = $value;
}
} elseif ($key === 'difficulty') {
// For difficulty, convert text label to numeric range
$difficulty_range = igny8_get_difficulty_numeric_range($value);
if ($difficulty_range) {
$where_conditions[] = "`{$key}` >= %d AND `{$key}` <= %d";
$where_values[] = $difficulty_range['min'];
$where_values[] = $difficulty_range['max'];
}
} else {
// For keyword search, use LIKE
$where_conditions[] = "`{$key}` LIKE %s";
$where_values[] = '%' . $wpdb->esc_like($value) . '%';
}
}
}
}
$where_clause = '';
if (!empty($where_conditions)) {
$where_clause = 'WHERE ' . implode(' AND ', $where_conditions);
}
// Build JOIN queries from column configurations
$join_queries = [];
foreach ($config_columns as $col_key => $col_config) {
if (isset($col_config['join_query'])) {
$join_query = str_replace(['{prefix}', '{table_name}'], [$wpdb->prefix, $table_name], $col_config['join_query']);
if (!in_array($join_query, $join_queries)) {
$join_queries[] = $join_query;
}
}
}
// Get total count for pagination (with JOINs)
$join_clause = '';
if (!empty($join_queries)) {
$join_clause = implode(' ', $join_queries);
}
$count_query = "SELECT COUNT(*) FROM `{$table_name}` {$join_clause} {$where_clause}";
if (!empty($where_values)) {
$total_count = $wpdb->get_var($wpdb->prepare($count_query, $where_values));
} else {
$total_count = $wpdb->get_var($count_query);
}
// Calculate pagination
$total_pages = ceil($total_count / $per_page);
$offset = ($page - 1) * $per_page;
// Build main query with JOINs and proper field selection
$select_fields = [];
// Add base table fields
$select_fields[] = "{$table_name}.*";
// Process column configurations to build select fields
foreach ($config_columns as $col_key => $col_config) {
if (isset($col_config['select_field'])) {
$select_fields[] = $col_config['select_field'];
}
}
// Build final query with JOINs
$select_clause = implode(', ', $select_fields);
$query = "SELECT {$select_clause} FROM `{$table_name}` {$join_clause} {$where_clause} ORDER BY {$table_name}.id DESC LIMIT %d OFFSET %d";
$query_values = array_merge($where_values, [$per_page, $offset]);
// Execute query
if (!empty($where_values)) {
$final_query = $wpdb->prepare($query, $query_values);
$results = $wpdb->get_results($final_query, ARRAY_A);
} else {
$final_query = $wpdb->prepare($query, $per_page, $offset);
$results = $wpdb->get_results($final_query, ARRAY_A);
}
// Format results for frontend - return complete table body HTML using PHP templating
$table_body_html = '';
// Load table configuration once (needed for both results and empty state)
$tables_config = require plugin_dir_path(__FILE__) . '../config/tables-config.php';
$GLOBALS['igny8_tables_config'] = $tables_config;
$table_config = igny8_get_dynamic_table_config($tableId);
$config_columns = $table_config['columns'] ?? [];
// Get column keys from humanize_columns, including date columns
$column_keys = $table_config['humanize_columns'] ?? array_keys($config_columns);
if ($results) {
foreach ($results as $row) {
$id = $row['id'] ?? 0;
// Use PHP templating instead of string concatenation
ob_start();
?>
|
prefix, $table_name], $calculation_query);
$query = str_replace('{table_name}.id', $row['id'], $query);
$value = $wpdb->get_var($query) ?? 0;
} else {
$value = 0;
}
} elseif ($col === 'cluster_id' || $col === 'keyword_cluster_id') {
// Check if this column has a display_field from JOIN query
$display_field = $column_config['display_field'] ?? null;
if ($display_field && isset($row[$display_field])) {
$value = $row[$display_field];
} else {
// Fallback to fetching cluster name by ID
$cluster_id = isset($row[$col]) ? $row[$col] : '';
$value = igny8_get_cluster_term_name($cluster_id);
}
} elseif ($col === 'source') {
// Handle source column - use actual field value
$value = isset($row[$col]) ? $row[$col] : '';
} elseif ($col === 'sector_id') {
$sector_id = isset($row[$col]) ? $row[$col] : '';
$value = igny8_get_sector_name($sector_id);
} elseif ($col === 'difficulty' || $col === 'avg_difficulty') {
$difficulty = isset($row[$col]) ? $row[$col] : '';
$value = igny8_get_difficulty_range_name($difficulty);
} elseif (in_array($col, ['created_at', 'created_date', 'last_audit', 'discovered_date', 'start_date'])) {
// Format created/audit/discovered/start date columns for all tables
$date_value = isset($row[$col]) ? $row[$col] : '';
$value = igny8_format_created_date($date_value);
} elseif (in_array($col, ['updated_at', 'updated_date', 'next_audit', 'end_date'])) {
// Format updated/next audit/end date columns for all tables
$date_value = isset($row[$col]) ? $row[$col] : '';
$value = igny8_format_updated_date($date_value);
} else {
$value = isset($row[$col]) ? $row[$col] : '';
// Apply proper case transformation to eligible columns
if (!empty($value) && igny8_should_apply_proper_case($col, $column_config)) {
$value = igny8_apply_proper_case($value, $col);
}
}
// Special handling for idea_title column in planner_ideas table
if ($col === 'idea_title' && $tableId === 'planner_ideas') {
$description = isset($row['idea_description']) ? $row['idea_description'] : '';
$image_prompts = isset($row['image_prompts']) ? $row['image_prompts'] : '';
// Format description for display (handle JSON vs plain text)
$display_description = '';
if (!empty($description)) {
$decoded = json_decode($description, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
// Format structured description for display
$display_description = igny8_format_structured_description_for_display($decoded);
} else {
// Use as plain text
$display_description = $description;
}
}
?>
|
|
Manual
|
|
|
|
No records found
|
$page,
'total_items' => intval($total_count),
'per_page' => $per_page,
'total_pages' => $total_pages
];
return [
'table_body_html' => $table_body_html,
'pagination' => $pagination_data
];
}
// Render table function
function igny8_render_table($table_id, $columns = []) {
// Load table configuration with AI-specific modifications
$tables_config = require plugin_dir_path(__FILE__) . '../config/tables-config.php';
$GLOBALS['igny8_tables_config'] = $tables_config;
$table_config = igny8_get_dynamic_table_config($table_id);
// Set variables for component
$module = explode('_', $table_id)[0];
$tab = explode('_', $table_id)[1] ?? '';
// Use config columns if provided, otherwise use passed columns
$config_columns = $table_config['columns'] ?? [];
// Convert associative array to indexed array with 'key' field, including date columns
if (!empty($config_columns)) {
$columns = [];
foreach ($config_columns as $key => $column_config) {
// Check if this column should be humanized
$humanize_columns = $table_config['humanize_columns'] ?? [];
$should_humanize = in_array($key, $humanize_columns);
$columns[] = [
'key' => $key,
'label' => $column_config['label'] ?? ($should_humanize ? igny8_humanize_label($key) : $key),
'sortable' => $column_config['sortable'] ?? false,
'type' => $column_config['type'] ?? 'text'
];
}
}
// Start output buffering to capture HTML
ob_start();
?>
'content', 'label' => 'Content', 'sortable' => true],
['key' => 'status', 'label' => 'Status', 'sortable' => true],
['key' => 'date', 'label' => 'Date', 'sortable' => true]
];
$module = $module ?? '';
$tab = $tab ?? '';
// Debug state: Table HTML rendered
if (function_exists('igny8_debug_state')) {
igny8_debug_state('TABLE_HTML_RENDERED', true, 'Table HTML rendered for ' . $table_id);
}
?>