1624 lines
64 KiB
PHP
1624 lines
64 KiB
PHP
<?php
|
|
/**
|
|
* ==========================
|
|
* 🔐 IGNY8 FILE RULE HEADER
|
|
* ==========================
|
|
* @file : module-debug.php
|
|
* @location : /debug/module-debug.php
|
|
* @type : Debug Tool
|
|
* @scope : Global
|
|
* @allowed : Module debugging, development tools
|
|
* @reusability : Single Use
|
|
* @notes : Module debug widgets and development tools
|
|
*/
|
|
|
|
// Prevent direct access
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
// Dev-only access guard
|
|
if (!defined('IGNY8_DEV') || IGNY8_DEV !== true) {
|
|
return;
|
|
}
|
|
|
|
// Only run if WordPress debugging is enabled
|
|
if (!WP_DEBUG) {
|
|
return;
|
|
}
|
|
|
|
// Evidence functions removed - no longer needed
|
|
|
|
|
|
// Always render module debug content, but control visibility via JavaScript
|
|
|
|
/**
|
|
* Get current module and submodule information
|
|
* Centralized function to ensure consistent module detection
|
|
*/
|
|
function igny8_get_current_module_info() {
|
|
// Try to get from global first (set by routing)
|
|
if (isset($GLOBALS['igny8_current_module'])) {
|
|
$current_module = $GLOBALS['igny8_current_module'];
|
|
} else {
|
|
// Fallback to URL parsing
|
|
$current_page = $_GET['page'] ?? '';
|
|
$current_module = 'planner'; // Default fallback
|
|
if (!empty($current_page) && strpos($current_page, 'igny8-') === 0) {
|
|
$current_module = str_replace('igny8-', '', $current_page);
|
|
}
|
|
}
|
|
|
|
// Get submodule from URL or global
|
|
$current_submodule = $_GET['sm'] ?? $GLOBALS['current_submodule'] ?? '';
|
|
|
|
// Build table_id - don't use hardcoded fallback
|
|
if (!empty($current_submodule)) {
|
|
$table_id = $current_module . '_' . $current_submodule;
|
|
} else {
|
|
// If no submodule detected, don't assume 'keywords' - return empty
|
|
$table_id = $current_module;
|
|
}
|
|
|
|
return [
|
|
'module' => $current_module,
|
|
'submodule' => $current_submodule,
|
|
'table_id' => $table_id,
|
|
'page' => $_GET['page'] ?? ''
|
|
];
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Track AI Logs State
|
|
*/
|
|
function igny8_track_ai_logs_state($module_info) {
|
|
// Check if AI functions are available
|
|
if (!function_exists('igny8_get_ai_setting')) {
|
|
return [
|
|
'status' => 'error',
|
|
'message' => 'AI system not available',
|
|
'details' => 'igny8_get_ai_setting function missing'
|
|
];
|
|
}
|
|
|
|
// Check AI mode
|
|
$ai_mode = igny8_get_ai_setting('planner_mode', 'manual');
|
|
$ai_enabled = $ai_mode === 'ai';
|
|
|
|
// Get recent AI logs (last 10 events)
|
|
$ai_logs = get_option('igny8_ai_logs', []);
|
|
$recent_logs = array_slice($ai_logs, 0, 10);
|
|
|
|
$log_count = count($recent_logs);
|
|
$error_count = 0;
|
|
$success_count = 0;
|
|
|
|
foreach ($recent_logs as $log) {
|
|
if ($log['status'] === 'error') {
|
|
$error_count++;
|
|
} elseif ($log['status'] === 'success') {
|
|
$success_count++;
|
|
}
|
|
}
|
|
|
|
$details = [
|
|
'AI Mode: ' . ($ai_enabled ? '✅ Enabled' : '❌ Disabled'),
|
|
'Recent Events: ' . $log_count,
|
|
'Success: ' . $success_count . ' | Errors: ' . $error_count,
|
|
'Module: ' . $module_info['module'],
|
|
'Submodule: ' . ($module_info['submodule'] ?: 'NONE')
|
|
];
|
|
|
|
if ($ai_enabled && $log_count > 0) {
|
|
$status = $error_count === 0 ? 'success' : ($error_count < $success_count ? 'warning' : 'error');
|
|
$message = "AI Logs: {$log_count} events ({$success_count} success, {$error_count} errors)";
|
|
} elseif ($ai_enabled) {
|
|
$status = 'success';
|
|
$message = 'AI Logs: Ready (no events yet)';
|
|
} else {
|
|
$status = 'warning';
|
|
$message = 'AI Logs: AI mode disabled';
|
|
}
|
|
|
|
return [
|
|
'status' => $status,
|
|
'message' => $message,
|
|
'details' => implode('<br>', $details)
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Track Database Validation State
|
|
*/
|
|
function igny8_track_database_validation_state() {
|
|
$module_info = igny8_get_current_module_info();
|
|
$table_id = $module_info['table_id'];
|
|
|
|
// Get actual database table name and fields
|
|
global $wpdb;
|
|
$table_name = igny8_get_table_name($table_id);
|
|
|
|
if (!$table_name) {
|
|
return [
|
|
'status' => 'error',
|
|
'message' => 'Table not found',
|
|
'details' => 'Table ID: ' . $table_id . ' - No table name mapping found'
|
|
];
|
|
}
|
|
|
|
// Get actual database fields
|
|
$db_fields = [];
|
|
$table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$table_name}'") !== null;
|
|
|
|
if ($table_exists) {
|
|
$columns = $wpdb->get_results("DESCRIBE {$table_name}");
|
|
foreach ($columns as $column) {
|
|
$db_fields[] = $column->Field;
|
|
}
|
|
} else {
|
|
return [
|
|
'status' => 'error',
|
|
'message' => 'Database table missing',
|
|
'details' => 'Table: ' . $table_name . ' does not exist in database'
|
|
];
|
|
}
|
|
|
|
// Validate table config fields
|
|
$table_errors = igny8_validate_table_config_fields($table_id, $db_fields);
|
|
$filter_errors = igny8_validate_filter_config_fields($table_id, $db_fields);
|
|
$form_errors = igny8_validate_form_config_fields($table_id, $db_fields);
|
|
|
|
$total_errors = count($table_errors) + count($filter_errors) + count($form_errors);
|
|
|
|
if ($total_errors === 0) {
|
|
return [
|
|
'status' => 'success',
|
|
'message' => 'Database validation passed',
|
|
'details' => 'Table: ✅<br>Filters: ✅<br>Forms: ✅<br>Automation: ✅'
|
|
];
|
|
} else {
|
|
$error_details = [];
|
|
|
|
// Format table errors
|
|
if (!empty($table_errors)) {
|
|
// Filter out config errors and show only field names
|
|
$field_errors = array_filter($table_errors, function($error) {
|
|
return !strpos($error, 'config') && !strpos($error, 'missing');
|
|
});
|
|
if (!empty($field_errors)) {
|
|
$error_details[] = 'Table:<br>' . implode(', ', $field_errors) . ' mismatch with db, doesn\'t exist';
|
|
}
|
|
}
|
|
|
|
// Format filter errors
|
|
if (!empty($filter_errors)) {
|
|
// Filter out config errors and show only field names
|
|
$field_errors = array_filter($filter_errors, function($error) {
|
|
return !strpos($error, 'config') && !strpos($error, 'missing');
|
|
});
|
|
if (!empty($field_errors)) {
|
|
$error_details[] = 'Filters:<br>' . implode(', ', $field_errors) . ' mismatch with db, doesn\'t exist';
|
|
}
|
|
}
|
|
|
|
// Format form errors
|
|
if (!empty($form_errors)) {
|
|
// Filter out config errors and show only field names
|
|
$field_errors = array_filter($form_errors, function($error) {
|
|
return !strpos($error, 'config') && !strpos($error, 'missing');
|
|
});
|
|
if (!empty($field_errors)) {
|
|
$error_details[] = 'Forms:<br>' . implode(', ', $field_errors) . ' mismatch with db, doesn\'t exist';
|
|
}
|
|
}
|
|
|
|
// If no field errors, show config errors
|
|
if (empty($error_details)) {
|
|
if (!empty($table_errors)) {
|
|
$error_details[] = 'Table:<br>' . implode(', ', $table_errors);
|
|
}
|
|
if (!empty($filter_errors)) {
|
|
$error_details[] = 'Filters:<br>' . implode(', ', $filter_errors);
|
|
}
|
|
if (!empty($form_errors)) {
|
|
$error_details[] = 'Forms:<br>' . implode(', ', $form_errors);
|
|
}
|
|
}
|
|
|
|
return [
|
|
'status' => 'error',
|
|
'message' => 'Database validation failed',
|
|
'details' => implode('<br><br>', $error_details)
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate table config fields against database schema
|
|
*/
|
|
function igny8_validate_table_config_fields($table_id, $db_fields) {
|
|
$errors = [];
|
|
|
|
try {
|
|
// Load config file directly
|
|
$config_path = plugin_dir_path(__FILE__) . '../modules/config/tables-config.php';
|
|
if (!file_exists($config_path)) {
|
|
return ['tables_config_file_missing'];
|
|
}
|
|
|
|
// Define constant to bypass access control
|
|
if (!defined('IGNY8_INCLUDE_CONFIG')) {
|
|
define('IGNY8_INCLUDE_CONFIG', true);
|
|
}
|
|
|
|
$tables_config = require $config_path;
|
|
if (!isset($tables_config[$table_id])) {
|
|
return ['table_config_not_found'];
|
|
}
|
|
|
|
$table_config = $tables_config[$table_id];
|
|
|
|
// Check columns
|
|
if (isset($table_config['columns'])) {
|
|
foreach ($table_config['columns'] as $column_name => $column_config) {
|
|
// For display columns, check the source_field instead of the column name
|
|
$field_to_check = $column_name;
|
|
if (isset($column_config['source_field'])) {
|
|
$field_to_check = $column_config['source_field'];
|
|
}
|
|
|
|
if (!in_array($field_to_check, $db_fields)) {
|
|
$errors[] = $column_name;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check humanize_columns
|
|
if (isset($table_config['humanize_columns'])) {
|
|
foreach ($table_config['humanize_columns'] as $column_name) {
|
|
if (!in_array($column_name, $db_fields)) {
|
|
$errors[] = $column_name;
|
|
}
|
|
}
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
$errors[] = 'config_load_error: ' . $e->getMessage();
|
|
}
|
|
|
|
return $errors;
|
|
}
|
|
|
|
/**
|
|
* Validate filter config fields against database schema
|
|
*/
|
|
function igny8_validate_filter_config_fields($table_id, $db_fields) {
|
|
$errors = [];
|
|
|
|
try {
|
|
// Load config file directly
|
|
$config_path = plugin_dir_path(__FILE__) . '../modules/config/filters-config.php';
|
|
if (!file_exists($config_path)) {
|
|
return ['filters_config_file_missing'];
|
|
}
|
|
|
|
// Define constant to bypass access control
|
|
if (!defined('IGNY8_INCLUDE_CONFIG')) {
|
|
define('IGNY8_INCLUDE_CONFIG', true);
|
|
}
|
|
|
|
$filters_config = require $config_path;
|
|
if (!isset($filters_config[$table_id])) {
|
|
return []; // No filters config is OK
|
|
}
|
|
|
|
$filter_config = $filters_config[$table_id];
|
|
|
|
foreach ($filter_config as $filter_name => $filter_data) {
|
|
if (isset($filter_data['field']) && !in_array($filter_data['field'], $db_fields)) {
|
|
$errors[] = $filter_data['field'];
|
|
}
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
$errors[] = 'config_load_error: ' . $e->getMessage();
|
|
}
|
|
|
|
return $errors;
|
|
}
|
|
|
|
/**
|
|
* Validate form config fields against database schema
|
|
*/
|
|
function igny8_validate_form_config_fields($table_id, $db_fields) {
|
|
$errors = [];
|
|
|
|
try {
|
|
// Load config file directly
|
|
$config_path = plugin_dir_path(__FILE__) . '../modules/config/forms-config.php';
|
|
if (!file_exists($config_path)) {
|
|
return ['forms_config_file_missing'];
|
|
}
|
|
|
|
// Define constant to bypass access control
|
|
if (!defined('IGNY8_INCLUDE_CONFIG')) {
|
|
define('IGNY8_INCLUDE_CONFIG', true);
|
|
}
|
|
|
|
$forms_config = require $config_path;
|
|
if (!isset($forms_config[$table_id])) {
|
|
return []; // No form config is OK
|
|
}
|
|
|
|
$form_config = $forms_config[$table_id];
|
|
if (!$form_config || !isset($form_config['fields'])) {
|
|
return []; // No form config is OK
|
|
}
|
|
|
|
foreach ($form_config['fields'] as $field) {
|
|
if (isset($field['name']) && !in_array($field['name'], $db_fields)) {
|
|
$errors[] = $field['name'];
|
|
}
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
$errors[] = 'config_load_error: ' . $e->getMessage();
|
|
}
|
|
|
|
return $errors;
|
|
}
|
|
|
|
function igny8_test_column_type_validation($config_data, $table_id) {
|
|
if (!isset($config_data[$table_id]['columns'])) {
|
|
return ['passed' => false, 'error' => 'no columns'];
|
|
}
|
|
|
|
$columns = $config_data[$table_id]['columns'];
|
|
foreach ($columns as $key => $column) {
|
|
if (!isset($column['type']) || !in_array($column['type'], ['text', 'number', 'date', 'select', 'textarea', 'enum'])) {
|
|
return ['passed' => false, 'error' => 'invalid type: ' . $key];
|
|
}
|
|
}
|
|
return ['passed' => true, 'error' => ''];
|
|
}
|
|
|
|
function igny8_test_filter_field_validation($config_data, $table_id) {
|
|
if (!isset($config_data[$table_id])) {
|
|
return ['passed' => false, 'error' => 'no filters'];
|
|
}
|
|
|
|
$filters = $config_data[$table_id];
|
|
foreach ($filters as $key => $filter) {
|
|
if (!isset($filter['field']) || empty($filter['field'])) {
|
|
return ['passed' => false, 'error' => 'missing field: ' . $key];
|
|
}
|
|
}
|
|
return ['passed' => true, 'error' => ''];
|
|
}
|
|
|
|
function igny8_test_field_validation_check($config_data, $table_id) {
|
|
if (!function_exists('igny8_get_form_config')) {
|
|
return ['passed' => false, 'error' => 'function missing'];
|
|
}
|
|
|
|
$form_config = igny8_get_form_config($table_id);
|
|
if (!$form_config || !isset($form_config['fields'])) {
|
|
return ['passed' => false, 'error' => 'no form fields'];
|
|
}
|
|
|
|
foreach ($form_config['fields'] as $field) {
|
|
if (!isset($field['name']) || !isset($field['type'])) {
|
|
return ['passed' => false, 'error' => 'invalid field structure'];
|
|
}
|
|
|
|
// Test foreign key relationships
|
|
if (isset($field['type']) && $field['type'] === 'select' && (isset($field['options']) || isset($field['source']))) {
|
|
$fk_result = igny8_test_foreign_key_relationship($field, $table_id);
|
|
if (!$fk_result['passed']) {
|
|
return ['passed' => false, 'error' => 'FK issue: ' . $field['name'] . ' - ' . $fk_result['error']];
|
|
}
|
|
}
|
|
}
|
|
return ['passed' => true, 'error' => ''];
|
|
}
|
|
|
|
function igny8_test_foreign_key_relationship($field, $table_id) {
|
|
// Check if this is a foreign key field (like cluster_id, category_id, etc.)
|
|
$field_name = $field['name'];
|
|
|
|
// Common foreign key patterns
|
|
$fk_patterns = ['cluster_id', 'category_id', 'parent_id', 'user_id', 'group_id'];
|
|
$is_foreign_key = false;
|
|
|
|
foreach ($fk_patterns as $pattern) {
|
|
if (strpos($field_name, $pattern) !== false) {
|
|
$is_foreign_key = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$is_foreign_key) {
|
|
return ['passed' => true, 'error' => '']; // Not a foreign key, skip test
|
|
}
|
|
|
|
// CRITICAL ISSUE: Check if field uses 'source' with custom select rendering
|
|
if (isset($field['source']) && $field['type'] === 'select') {
|
|
// This is the cluster_id issue - custom select button vs form submission mismatch
|
|
return ['passed' => false, 'error' => 'custom select rendering - form submission mismatch'];
|
|
}
|
|
|
|
return ['passed' => true, 'error' => ''];
|
|
}
|
|
|
|
function igny8_test_query_syntax_validation($config_data, $table_id) {
|
|
if (!isset($config_data[$table_id])) {
|
|
return ['passed' => false, 'error' => 'no kpi config'];
|
|
}
|
|
|
|
$kpis = $config_data[$table_id];
|
|
foreach ($kpis as $key => $kpi) {
|
|
if (!isset($kpi['query']) || empty($kpi['query'])) {
|
|
return ['passed' => false, 'error' => 'missing query: ' . $key];
|
|
}
|
|
|
|
// Check for basic SQL syntax
|
|
if (!preg_match('/SELECT.*FROM.*\{table_name\}/i', $kpi['query'])) {
|
|
return ['passed' => false, 'error' => 'invalid query syntax: ' . $key];
|
|
}
|
|
}
|
|
return ['passed' => true, 'error' => ''];
|
|
}
|
|
|
|
/**
|
|
* Track Routing State
|
|
*/
|
|
function igny8_track_routing_state() {
|
|
$module_info = igny8_get_current_module_info();
|
|
$current_page = $_GET['page'] ?? '';
|
|
$current_submodule = $_GET['sm'] ?? '';
|
|
|
|
$routing_ok = !empty($current_page) && strpos($current_page, 'igny8-') === 0;
|
|
$submodule_ok = !empty($current_submodule);
|
|
|
|
return [
|
|
'status' => $routing_ok ? 'success' : 'error',
|
|
'message' => "Routing: {$current_page}" . ($submodule_ok ? " → {$current_submodule}" : ""),
|
|
'details' => "Table ID: {$module_info['table_id']}"
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Track Initial Render State
|
|
*/
|
|
function igny8_track_initial_render_state() {
|
|
$module_info = igny8_get_current_module_info();
|
|
$table_id = $module_info['table_id'];
|
|
|
|
$render_functions = [
|
|
'igny8_render_filters' => 'Filters',
|
|
'igny8_render_table_actions' => 'Actions',
|
|
'igny8_render_pagination' => 'Pagination'
|
|
];
|
|
|
|
$working_functions = 0;
|
|
$total_functions = count($render_functions);
|
|
$details = [];
|
|
|
|
foreach ($render_functions as $function => $name) {
|
|
if (function_exists($function)) {
|
|
$working_functions++;
|
|
$details[] = "{$name}: ✅";
|
|
} else {
|
|
$details[] = "{$name}: ❌";
|
|
}
|
|
}
|
|
|
|
// Test form rendering system
|
|
$form_tests = igny8_test_form_rendering_system($table_id);
|
|
$details[] = "Form Rendering: " . ($form_tests['passed'] ? '✅' : '❌ ' . $form_tests['error']);
|
|
if ($form_tests['passed']) {
|
|
$working_functions++;
|
|
}
|
|
$total_functions++;
|
|
|
|
return [
|
|
'status' => $working_functions === $total_functions ? 'success' : 'warning',
|
|
'message' => "Render functions: {$working_functions}/{$total_functions}",
|
|
'details' => implode('<br>', $details)
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Test form rendering system
|
|
*/
|
|
function igny8_test_form_rendering_system($table_id) {
|
|
// Test 1: Form config function exists
|
|
if (!function_exists('igny8_get_form_config')) {
|
|
return ['passed' => false, 'error' => 'igny8_get_form_config missing'];
|
|
}
|
|
|
|
// Test 2: Form rendering function exists
|
|
if (!function_exists('igny8_render_inline_form_row')) {
|
|
return ['passed' => false, 'error' => 'igny8_render_inline_form_row missing'];
|
|
}
|
|
|
|
// Test 3: Form field rendering functions exist
|
|
$field_functions = [
|
|
'igny8_render_form_field',
|
|
'igny8_render_text_field',
|
|
'igny8_render_number_field',
|
|
'igny8_render_select_field',
|
|
'igny8_render_textarea_field'
|
|
];
|
|
|
|
foreach ($field_functions as $func) {
|
|
if (!function_exists($func)) {
|
|
return ['passed' => false, 'error' => "{$func} missing"];
|
|
}
|
|
}
|
|
|
|
// Test 4: Form config loads for current table
|
|
try {
|
|
$form_config = igny8_get_form_config($table_id);
|
|
if (!$form_config) {
|
|
return ['passed' => false, 'error' => 'No form config for table'];
|
|
}
|
|
|
|
if (empty($form_config['fields'])) {
|
|
return ['passed' => false, 'error' => 'Empty form fields'];
|
|
}
|
|
|
|
// Test 5: Form can render without errors
|
|
$test_output = igny8_render_inline_form_row($table_id, 'add', []);
|
|
if (empty($test_output) || strpos($test_output, 'Form not configured') !== false) {
|
|
return ['passed' => false, 'error' => 'Form render failed'];
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
return ['passed' => false, 'error' => 'Form config error: ' . $e->getMessage()];
|
|
}
|
|
|
|
return ['passed' => true, 'error' => ''];
|
|
}
|
|
|
|
/**
|
|
* Track Table Rendering State
|
|
*/
|
|
function igny8_track_table_rendering_state() {
|
|
$module_info = igny8_get_current_module_info();
|
|
$table_id = $module_info['table_id'];
|
|
|
|
// Test 1: Table render function exists
|
|
if (!function_exists('igny8_render_table')) {
|
|
return [
|
|
'status' => 'error',
|
|
'message' => 'Table render function missing',
|
|
'details' => 'igny8_render_table function not found'
|
|
];
|
|
}
|
|
|
|
// Test 2: AJAX table loading functions exist
|
|
$ajax_functions = [
|
|
'igny8_get_table_data' => 'Main table data loader',
|
|
'igny8_ajax_load_table_data' => 'Submodule table loader'
|
|
];
|
|
|
|
$working_ajax = 0;
|
|
$total_ajax = count($ajax_functions);
|
|
$ajax_details = [];
|
|
|
|
foreach ($ajax_functions as $func => $name) {
|
|
if (function_exists($func)) {
|
|
$working_ajax++;
|
|
$ajax_details[] = "{$name}: ✅";
|
|
} else {
|
|
$ajax_details[] = "{$name}: ❌";
|
|
}
|
|
}
|
|
|
|
// Test 3: Check if table has actually loaded data
|
|
$table_data_status = igny8_check_table_data_loaded($table_id);
|
|
|
|
// Test 4: AJAX states tracking (only meaningful after AJAX has run)
|
|
$ajax_states = igny8_track_ajax_table_states();
|
|
|
|
$total_tests = 4;
|
|
$passed_tests = ($working_ajax === $total_ajax ? 1 : 0) +
|
|
($table_data_status['passed'] ? 1 : 0) +
|
|
($ajax_states['passed'] ? 1 : 0) + 1; // +1 for function existence
|
|
|
|
$details = [
|
|
'Function exists: ✅',
|
|
'AJAX functions: ' . $working_ajax . '/' . $total_ajax,
|
|
implode('<br>', $ajax_details),
|
|
'Table data loaded: ' . ($table_data_status['passed'] ? '✅' : '⏳ ' . $table_data_status['message']),
|
|
'AJAX states: ' . ($ajax_states['passed'] ? '✅' : '⏳ ' . $ajax_states['error']),
|
|
$ajax_states['details']
|
|
];
|
|
|
|
return [
|
|
'status' => $passed_tests === $total_tests ? 'success' : ($passed_tests > 2 ? 'warning' : 'error'),
|
|
'message' => "Table rendering: {$passed_tests}/{$total_tests} tests passed",
|
|
'details' => implode('<br>', $details)
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Check if table has actually loaded data
|
|
*/
|
|
function igny8_check_table_data_loaded($table_id) {
|
|
// Check if there's recent AJAX response data
|
|
if (isset($GLOBALS['igny8_last_ajax_response'])) {
|
|
$response = $GLOBALS['igny8_last_ajax_response'];
|
|
$time_diff = time() - $response['timestamp'];
|
|
|
|
// If response is recent (within 30 seconds) and has data
|
|
if ($time_diff < 30 && !empty($response['data']['items'])) {
|
|
return [
|
|
'passed' => true,
|
|
'message' => 'Data loaded (' . count($response['data']['items']) . ' rows)'
|
|
];
|
|
}
|
|
}
|
|
|
|
// Check if AJAX states indicate successful loading
|
|
if (isset($GLOBALS['igny8_debug_states']['TABLE_AJAX_RESPONSE_OK'])) {
|
|
$state = $GLOBALS['igny8_debug_states']['TABLE_AJAX_RESPONSE_OK'];
|
|
if ($state['status'] === true) {
|
|
return [
|
|
'passed' => true,
|
|
'message' => 'AJAX response successful'
|
|
];
|
|
}
|
|
}
|
|
|
|
// If no data loaded yet
|
|
return [
|
|
'passed' => false,
|
|
'message' => 'Waiting for AJAX data load'
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Debug state helper function - stores debug states in globals
|
|
*/
|
|
function igny8_debug_state($stage, $ok, $msg) {
|
|
if (!isset($GLOBALS['igny8_debug_states'])) {
|
|
$GLOBALS['igny8_debug_states'] = [];
|
|
}
|
|
|
|
$GLOBALS['igny8_debug_states'][$stage] = [
|
|
'status' => $ok,
|
|
'message' => $msg,
|
|
'timestamp' => time()
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Track AJAX table states
|
|
*/
|
|
function igny8_track_ajax_table_states() {
|
|
// The 4 AJAX states that indicate successful table loading
|
|
$ajax_states = [
|
|
'AJAX_NONCE_VALIDATED' => false,
|
|
'USER_CAPABILITY_OK' => false,
|
|
'TABLE_AJAX_RESPONSE_OK' => false,
|
|
'TABLE_AJAX_REQUEST_SENT' => false
|
|
];
|
|
|
|
$details = [];
|
|
$passed_states = 0;
|
|
|
|
// Check each AJAX state
|
|
foreach ($ajax_states as $state_name => $state_value) {
|
|
// Check if this state has been set by AJAX functions
|
|
if (isset($GLOBALS['igny8_debug_states'][$state_name])) {
|
|
$state_data = $GLOBALS['igny8_debug_states'][$state_name];
|
|
if ($state_data['status'] === true) {
|
|
$passed_states++;
|
|
$details[] = "{$state_name}: ✅";
|
|
} else {
|
|
$details[] = "{$state_name}: ❌ " . $state_data['message'];
|
|
}
|
|
} else {
|
|
$details[] = "{$state_name}: ⏳ Not triggered yet";
|
|
}
|
|
}
|
|
|
|
// Check if AJAX tracking system is available
|
|
if (!function_exists('igny8_debug_state')) {
|
|
return [
|
|
'passed' => false,
|
|
'error' => 'AJAX tracking system not available',
|
|
'details' => implode('<br>', $details)
|
|
];
|
|
}
|
|
|
|
// If no states have been triggered yet (page just loaded)
|
|
if ($passed_states === 0 && !isset($GLOBALS['igny8_debug_states'])) {
|
|
return [
|
|
'passed' => true,
|
|
'error' => 'AJAX tracking ready (no requests yet)',
|
|
'details' => implode('<br>', $details)
|
|
];
|
|
}
|
|
|
|
return [
|
|
'passed' => $passed_states === 4,
|
|
'error' => $passed_states === 4 ? '' : "Only {$passed_states}/4 states passed",
|
|
'details' => implode('<br>', $details)
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Track Filters State
|
|
*/
|
|
function igny8_track_filters_state() {
|
|
$module_info = igny8_get_current_module_info();
|
|
$table_id = $module_info['table_id'];
|
|
|
|
// Test 1: Filter render function exists
|
|
if (!function_exists('igny8_render_filters')) {
|
|
return [
|
|
'status' => 'error',
|
|
'message' => 'Filter render function missing',
|
|
'details' => 'igny8_render_filters function not found'
|
|
];
|
|
}
|
|
|
|
// Test 2: Filter config loads for current table (direct method)
|
|
try {
|
|
// Load filters config directly like igny8_render_filters() does
|
|
$filters_config_path = plugin_dir_path(__FILE__) . '../modules/config/filters-config.php';
|
|
if (!file_exists($filters_config_path)) {
|
|
return [
|
|
'status' => 'error',
|
|
'message' => 'Filter config file missing',
|
|
'details' => 'filters-config.php not found at: ' . $filters_config_path
|
|
];
|
|
}
|
|
|
|
$filters_config = require $filters_config_path;
|
|
$filter_config = $filters_config[$table_id] ?? [];
|
|
|
|
if (empty($filter_config)) {
|
|
return [
|
|
'status' => 'warning',
|
|
'message' => 'No filter config for table',
|
|
'details' => 'Filter config not found for: ' . $table_id
|
|
];
|
|
}
|
|
|
|
$filter_count = count($filter_config);
|
|
|
|
} catch (Exception $e) {
|
|
return [
|
|
'status' => 'error',
|
|
'message' => 'Filter config error',
|
|
'details' => 'Error loading filter config: ' . $e->getMessage()
|
|
];
|
|
}
|
|
|
|
// Test 4: Filter can render without errors
|
|
try {
|
|
$test_output = igny8_render_filters($table_id);
|
|
if (empty($test_output) || strpos($test_output, 'Filter not configured') !== false) {
|
|
$render_test = '❌ Filter render failed';
|
|
} else {
|
|
$render_test = '✅ Filter renders OK';
|
|
}
|
|
} catch (Exception $e) {
|
|
$render_test = '❌ Render error: ' . $e->getMessage();
|
|
}
|
|
|
|
$details = [
|
|
'Render function: ✅',
|
|
'Config file: ✅',
|
|
'Filter config: ✅ (' . $filter_count . ' filters)',
|
|
'Render test: ' . $render_test
|
|
];
|
|
|
|
return [
|
|
'status' => 'success',
|
|
'message' => "Filters: {$filter_count} filters configured",
|
|
'details' => implode('<br>', $details)
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Track Forms State
|
|
*/
|
|
function igny8_track_forms_state() {
|
|
$module_info = igny8_get_current_module_info();
|
|
$table_id = $module_info['table_id'];
|
|
|
|
// Test 1: Form render function exists
|
|
if (!function_exists('igny8_render_inline_form_row')) {
|
|
return [
|
|
'status' => 'error',
|
|
'message' => 'Form render function missing',
|
|
'details' => 'igny8_render_inline_form_row function not found'
|
|
];
|
|
}
|
|
|
|
// Test 2: Form config function exists
|
|
if (!function_exists('igny8_get_form_config')) {
|
|
return [
|
|
'status' => 'error',
|
|
'message' => 'Form config function missing',
|
|
'details' => 'igny8_get_form_config function not found'
|
|
];
|
|
}
|
|
|
|
// Test 3: Form config loads for current table
|
|
try {
|
|
$form_config = igny8_get_form_config($table_id);
|
|
if (!$form_config) {
|
|
return [
|
|
'status' => 'warning',
|
|
'message' => 'No form config for table',
|
|
'details' => 'Form config not found for: ' . $table_id
|
|
];
|
|
}
|
|
|
|
if (empty($form_config['fields'])) {
|
|
return [
|
|
'status' => 'warning',
|
|
'message' => 'Empty form fields',
|
|
'details' => 'Form config exists but has no fields'
|
|
];
|
|
}
|
|
|
|
$field_count = count($form_config['fields']);
|
|
|
|
} catch (Exception $e) {
|
|
return [
|
|
'status' => 'error',
|
|
'message' => 'Form config error',
|
|
'details' => 'Error loading form config: ' . $e->getMessage()
|
|
];
|
|
}
|
|
|
|
// Test 4: Form can render without errors
|
|
try {
|
|
$test_output = igny8_render_inline_form_row($table_id, 'add', []);
|
|
if (empty($test_output) || strpos($test_output, 'Form not configured') !== false) {
|
|
$render_test = '❌ Form render failed';
|
|
} else {
|
|
$render_test = '✅ Form renders OK';
|
|
}
|
|
} catch (Exception $e) {
|
|
$render_test = '❌ Render error: ' . $e->getMessage();
|
|
}
|
|
|
|
$details = [
|
|
'Function exists: ✅',
|
|
'Config function: ✅',
|
|
'Form config: ✅ (' . $field_count . ' fields)',
|
|
'Render test: ' . $render_test
|
|
];
|
|
|
|
return [
|
|
'status' => 'success',
|
|
'message' => "Forms: {$field_count} fields configured",
|
|
'details' => implode('<br>', $details)
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Track Automation Validation State - Submodule Specific
|
|
*/
|
|
function igny8_track_automation_validation_state() {
|
|
$module_info = igny8_get_current_module_info();
|
|
$current_submodule = $module_info['submodule'];
|
|
|
|
$automation_errors = [];
|
|
$automation_functions = [];
|
|
$working_functions = 0;
|
|
|
|
try {
|
|
// Define submodule-specific automation functions
|
|
switch ($current_submodule) {
|
|
case 'keywords':
|
|
$automation_functions = [
|
|
'igny8_update_cluster_metrics' => 'Cluster metrics update',
|
|
'igny8_handle_keyword_cluster_update' => 'Keyword cluster updates',
|
|
'igny8_bulk_delete_keywords' => 'Bulk keyword deletion',
|
|
'igny8_bulk_map_keywords' => 'Bulk keyword mapping',
|
|
'igny8_bulk_unmap_keywords' => 'Bulk keyword unmapping',
|
|
'igny8_ajax_ai_cluster_keywords' => 'AI cluster creation',
|
|
'igny8_workflow_triggers' => 'Workflow triggers',
|
|
'igny8_ajax_import_keywords' => 'Keyword import automation'
|
|
];
|
|
break;
|
|
|
|
case 'clusters':
|
|
$automation_functions = [
|
|
'igny8_update_cluster_metrics' => 'Cluster metrics update',
|
|
'igny8_auto_create_cluster_term' => 'Cluster taxonomy creation',
|
|
'igny8_auto_update_cluster_term' => 'Cluster taxonomy updates',
|
|
'igny8_handle_content_cluster_association' => 'Content cluster associations',
|
|
'igny8_ajax_ai_generate_ideas' => 'AI idea generation',
|
|
'igny8_workflow_triggers' => 'Workflow triggers'
|
|
];
|
|
break;
|
|
|
|
case 'ideas':
|
|
$automation_functions = [
|
|
'igny8_update_idea_metrics' => 'Idea metrics update',
|
|
'igny8_create_task_from_idea' => 'Task creation from ideas',
|
|
'igny8_workflow_triggers' => 'Workflow triggers',
|
|
'igny8_write_log' => 'Automation logging',
|
|
'igny8_ajax_ai_generate_ideas' => 'AI idea generation',
|
|
'igny8_ajax_ai_generate_content' => 'AI content generation'
|
|
];
|
|
break;
|
|
|
|
default:
|
|
// Fallback to basic automation functions
|
|
$automation_functions = [
|
|
'igny8_update_cluster_metrics' => 'Cluster metrics update',
|
|
'igny8_handle_keyword_cluster_update' => 'Keyword cluster updates',
|
|
'igny8_workflow_triggers' => 'Workflow triggers'
|
|
];
|
|
}
|
|
|
|
// Check if each automation function exists
|
|
$function_details = [];
|
|
foreach ($automation_functions as $function => $description) {
|
|
if (function_exists($function)) {
|
|
$working_functions++;
|
|
$function_details[] = "{$description}: ✅";
|
|
} else {
|
|
// Check if this is a known missing function
|
|
if (strpos($description, 'MISSING') !== false) {
|
|
$automation_errors[] = "{$description}: ❌ (Function never implemented - referenced in docs but missing from code)";
|
|
$function_details[] = "{$description}: ❌ (NOT IMPLEMENTED)";
|
|
} else {
|
|
$automation_errors[] = "{$description}: ❌ ({$function} missing)";
|
|
$function_details[] = "{$description}: ❌";
|
|
}
|
|
}
|
|
}
|
|
|
|
$total_functions = count($automation_functions);
|
|
|
|
} catch (Exception $e) {
|
|
$automation_errors[] = 'Automation check error: ' . $e->getMessage();
|
|
}
|
|
|
|
if (empty($automation_errors)) {
|
|
return [
|
|
'status' => 'success',
|
|
'message' => "Automation: {$working_functions}/{$total_functions} functions active",
|
|
'details' => implode('<br>', $function_details)
|
|
];
|
|
} else {
|
|
return [
|
|
'status' => $working_functions > ($total_functions / 2) ? 'warning' : 'error',
|
|
'message' => "Automation: {$working_functions}/{$total_functions} functions active",
|
|
'details' => implode('<br>', $function_details) . '<br><br>Issues:<br>' . implode('<br>', $automation_errors)
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Track Database Pre-Fetch State
|
|
*/
|
|
function igny8_track_db_prefetch_state() {
|
|
global $wpdb;
|
|
$module_info = igny8_get_current_module_info();
|
|
|
|
// Get table name
|
|
if (function_exists('igny8_get_table_name')) {
|
|
$table_name = igny8_get_table_name($module_info['table_id']);
|
|
} else {
|
|
$table_name = $wpdb->prefix . 'igny8_' . str_replace('_', '', $module_info['table_id']);
|
|
}
|
|
|
|
// Check if table exists
|
|
$table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$table_name}'") !== null;
|
|
|
|
if ($table_exists) {
|
|
$row_count = $wpdb->get_var("SELECT COUNT(*) FROM {$table_name}");
|
|
return [
|
|
'status' => 'success',
|
|
'message' => "Table: {$table_name} ({$row_count} rows)",
|
|
'details' => "Database connection: ✅<br>Table exists: ✅"
|
|
];
|
|
} else {
|
|
return [
|
|
'status' => 'error',
|
|
'message' => "Table not found: {$table_name}",
|
|
'details' => "Database connection: " . (!empty($wpdb->dbh) ? '✅' : '❌') . "<br>Table exists: ❌"
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Track Frontend Initialization State
|
|
*/
|
|
function igny8_track_frontend_init_state() {
|
|
// Check if core.js is enqueued
|
|
$core_js_enqueued = wp_script_is('igny8-admin-js', 'enqueued');
|
|
|
|
// Check if required DOM elements exist (this will be checked by JavaScript)
|
|
return [
|
|
'status' => $core_js_enqueued ? 'success' : 'warning',
|
|
'message' => "Frontend JS: " . ($core_js_enqueued ? 'Enqueued' : 'Not enqueued'),
|
|
'details' => "Core.js loaded: " . ($core_js_enqueued ? '✅' : '❌') . "<br>DOM elements: Checked by JS"
|
|
];
|
|
}
|
|
|
|
|
|
|
|
// Get module debug content using consolidated evidence system
|
|
function igny8_get_module_debug_content() {
|
|
// Get current module information
|
|
$module_info = igny8_get_current_module_info();
|
|
$module_name = ucfirst($module_info['module']);
|
|
$current_submodule = $module_info['submodule'];
|
|
|
|
// Track debug states - split into component states and automation states
|
|
$component_states = [
|
|
'database' => igny8_track_database_validation_state(),
|
|
'table' => igny8_track_initial_render_state(),
|
|
'filters' => igny8_track_filters_state(),
|
|
'forms' => igny8_track_forms_state()
|
|
];
|
|
|
|
$automation_states = [
|
|
'automation' => igny8_track_automation_validation_state(),
|
|
'ai_logs' => igny8_track_ai_logs_state($module_info)
|
|
];
|
|
|
|
ob_start();
|
|
?>
|
|
<?php
|
|
$debug_enabled = get_option('igny8_debug_enabled', false);
|
|
?>
|
|
<div class="igny8-card igny8-mb-20" id="igny8-module-debug-container" style="display: <?php echo $debug_enabled ? 'block' : 'none'; ?>;">
|
|
<div class="igny8-card-header">
|
|
<div class="igny8-card-title">
|
|
<span style="font-size: 20px; margin-right: 8px;">🔍</span>
|
|
Module Debug: <?php echo esc_html($module_name); ?>
|
|
</div>
|
|
<button class="igny8-btn igny8-btn-secondary" id="toggle-module-debug-btn">Toggle Debug</button>
|
|
</div>
|
|
|
|
<div class="igny8-card-body" id="module-debug-body">
|
|
<!-- Module Information -->
|
|
<div class="igny8-flex" style="justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
|
<div>
|
|
<h4>Module Information</h4>
|
|
|
|
|
|
<div class="igny8-flex" style="background: #f0f0f0; padding: 10px; border-radius: 4px; margin-top: 10px; font-family: monospace; font-size: 12px;">
|
|
<strong>Module:</strong> <?php echo esc_html($module_info['module']); ?><br>
|
|
<strong>Submodule:</strong> <?php echo esc_html($module_info['submodule'] ?: 'NONE'); ?><br>
|
|
<strong>Table ID:</strong> <?php echo esc_html($module_info['table_id']); ?><br>
|
|
<strong>Page:</strong> <?php echo esc_html($module_info['page']); ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Component States -->
|
|
<div class="igny8-debug-section">
|
|
<h4>Submodule Components Render</h4>
|
|
<div class="igny8-grid-4">
|
|
<?php foreach ($component_states as $state_key => $state_data): ?>
|
|
<div class="igny8-debug-item" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; background: <?php echo $state_data['status'] === 'success' ? '#d4edda' : ($state_data['status'] === 'warning' ? '#fff3cd' : '#f8d7da'); ?>;">
|
|
<div style="font-weight: bold; margin-bottom: 5px;">
|
|
<?php echo ucwords(str_replace('_', ' ', $state_key)); ?>
|
|
<span style="color: <?php echo $state_data['status'] === 'success' ? 'green' : ($state_data['status'] === 'warning' ? 'orange' : 'red'); ?>;">
|
|
<?php echo $state_data['status'] === 'success' ? '✅' : ($state_data['status'] === 'warning' ? '⚠️' : '❌'); ?>
|
|
</span>
|
|
</div>
|
|
<div style="font-size: 12px; margin-bottom: 3px;">
|
|
<?php echo esc_html($state_data['message']); ?>
|
|
</div>
|
|
<div style="font-size: 11px; color: #666;">
|
|
<?php echo $state_data['details']; ?>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Automation States -->
|
|
<div class="igny8-debug-section" style="margin-top: 20px;">
|
|
<h4>Automation & AI Systems</h4>
|
|
<div class="igny8-grid-5">
|
|
<?php foreach ($automation_states as $state_key => $state_data): ?>
|
|
<?php if ($state_key === 'ai_logs') continue; // Skip AI logs, will be displayed separately below ?>
|
|
<div class="igny8-debug-item" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; background: <?php echo $state_data['status'] === 'success' ? '#d4edda' : ($state_data['status'] === 'warning' ? '#fff3cd' : '#f8d7da'); ?>;">
|
|
<div style="font-weight: bold; margin-bottom: 5px;">
|
|
<?php echo ucwords(str_replace('_', ' ', $state_key)); ?>
|
|
<span style="color: <?php echo $state_data['status'] === 'success' ? 'green' : ($state_data['status'] === 'warning' ? 'orange' : 'red'); ?>;">
|
|
<?php echo $state_data['status'] === 'success' ? '✅' : ($state_data['status'] === 'warning' ? '⚠️' : '❌'); ?>
|
|
</span>
|
|
</div>
|
|
<div style="font-size: 12px; margin-bottom: 3px;">
|
|
<?php echo esc_html($state_data['message']); ?>
|
|
</div>
|
|
<div style="font-size: 11px; color: #666;">
|
|
<?php echo $state_data['details']; ?>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- AI Logs Section (Standalone, below debug card) -->
|
|
<div class="igny8-card igny8-mb-20" id="igny8-ai-logs-container" style="display: <?php echo $debug_enabled ? 'block' : 'none'; ?>;">
|
|
<div class="igny8-card-header">
|
|
<div class="igny8-card-title">
|
|
<span style="font-size: 20px; margin-right: 8px;">🤖</span>
|
|
AI Logs & Events
|
|
</div>
|
|
<div style="display: flex; gap: 8px;">
|
|
<button id="refresh-ai-logs" class="igny8-btn igny8-btn-secondary igny8-btn-sm">
|
|
<span class="dashicons dashicons-update"></span> Refresh
|
|
</button>
|
|
<button id="clear-ai-logs" class="igny8-btn igny8-btn-outline igny8-btn-sm">
|
|
<span class="dashicons dashicons-trash"></span> Clear
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="igny8-card-body" style="padding: 20px;">
|
|
<div style="display: flex; gap: 10px; margin-bottom: 15px; font-size: 13px;">
|
|
<div style="flex: 1; padding: 10px; background: #f8f9fa; border-radius: 4px;">
|
|
<strong>Status:</strong> <span id="ai-logs-message">Loading...</span>
|
|
</div>
|
|
<div style="flex: 2; padding: 10px; background: #f8f9fa; border-radius: 4px;">
|
|
<span id="ai-logs-details">Initializing...</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- AI Events Container with persistent log boxes -->
|
|
<div id="ai-logs-events" style="max-height: 400px; overflow-y: auto; padding: 10px; background: #fff; border: 1px solid #ddd; border-radius: 4px;">
|
|
<div style="text-align: center; color: #666; padding: 20px;">
|
|
<span class="dashicons dashicons-update" style="animation: spin 2s linear infinite; font-size: 24px;"></span>
|
|
<br><br>Loading AI events...
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Image Generation Logs Section (only on drafts page) -->
|
|
<?php if ($current_submodule === 'drafts'): ?>
|
|
<div class="igny8-card igny8-mb-20" id="igny8-image-gen-logs-container" style="display: <?php echo $debug_enabled ? 'block' : 'none'; ?>;">
|
|
<div class="igny8-card-header">
|
|
<div class="igny8-card-title">
|
|
<span style="font-size: 20px; margin-right: 8px;">🎨</span>
|
|
Image Generation Logs
|
|
</div>
|
|
<div style="display: flex; gap: 8px;">
|
|
<button id="refresh-image-gen" class="igny8-btn igny8-btn-secondary igny8-btn-sm">
|
|
<span class="dashicons dashicons-update"></span> Refresh
|
|
</button>
|
|
<button id="clear-image-gen" class="igny8-btn igny8-btn-outline igny8-btn-sm">
|
|
<span class="dashicons dashicons-trash"></span> Clear
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="igny8-card-body" style="padding: 20px;">
|
|
<div style="display: flex; gap: 10px; margin-bottom: 15px; font-size: 13px;">
|
|
<div style="flex: 1; padding: 10px; background: #f8f9fa; border-radius: 4px;">
|
|
<strong>Status:</strong> <span id="image-gen-message">Ready for image generation</span>
|
|
</div>
|
|
<div style="flex: 2; padding: 10px; background: #f8f9fa; border-radius: 4px;">
|
|
<span id="image-gen-details">Waiting for image generation process</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Image Generation Events Container with persistent log boxes -->
|
|
<div id="image-gen-events" style="max-height: 400px; overflow-y: auto; padding: 10px; background: #fff; border: 1px solid #ddd; border-radius: 4px;">
|
|
<div style="text-align: center; color: #666; padding: 20px;">
|
|
<span class="dashicons dashicons-format-image" style="font-size: 24px;"></span>
|
|
<br><br>No image generation events yet
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
<script>
|
|
// Module Debug Toggle Functionality with localStorage
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const toggleBtn = document.getElementById('toggle-module-debug-btn');
|
|
const debugBody = document.getElementById('module-debug-body');
|
|
const aiLogsContainer = document.getElementById('igny8-ai-logs-container');
|
|
const imageGenContainer = document.getElementById('igny8-image-gen-logs-container');
|
|
|
|
// Load saved state from localStorage
|
|
const debugState = localStorage.getItem('igny8_module_debug_state');
|
|
if (debugState === 'collapsed') {
|
|
debugBody.style.display = 'none';
|
|
}
|
|
|
|
// Toggle functionality
|
|
if (toggleBtn && debugBody) {
|
|
toggleBtn.addEventListener('click', function() {
|
|
if (debugBody.style.display === 'none') {
|
|
debugBody.style.display = 'block';
|
|
localStorage.setItem('igny8_module_debug_state', 'expanded');
|
|
} else {
|
|
debugBody.style.display = 'none';
|
|
localStorage.setItem('igny8_module_debug_state', 'collapsed');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Real-time AI Logs functionality
|
|
const aiLogsMessage = document.getElementById('ai-logs-message');
|
|
const aiLogsDetails = document.getElementById('ai-logs-details');
|
|
const aiLogsEvents = document.getElementById('ai-logs-events');
|
|
const refreshBtn = document.getElementById('refresh-ai-logs');
|
|
const clearBtn = document.getElementById('clear-ai-logs');
|
|
|
|
if (!aiLogsEvents) return;
|
|
|
|
let refreshInterval;
|
|
|
|
// Load AI logs and stats
|
|
function loadAILogs() {
|
|
const ajaxUrl = window.ajaxurl || (window.IGNY8_PAGE?.ajaxUrl) || '/wp-admin/admin-ajax.php';
|
|
fetch(ajaxUrl, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: 'action=igny8_get_ai_logs&nonce=' + (window.IGNY8_PAGE?.nonce || '')
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
updateAIStats(data.data);
|
|
displayAIEvents(data.data);
|
|
} else {
|
|
console.error('Error loading AI logs:', data.data);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading AI logs:', error);
|
|
});
|
|
}
|
|
|
|
// Expose refresh function globally for other scripts
|
|
window.refreshAILogs = loadAILogs;
|
|
|
|
// Update module context dynamically based on current URL
|
|
function updateModuleContext() {
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const page = urlParams.get('page') || '';
|
|
const submodule = urlParams.get('sm') || '';
|
|
|
|
// Extract module from page parameter
|
|
let module = 'planner'; // default
|
|
if (page && page.startsWith('igny8-')) {
|
|
module = page.replace('igny8-', '');
|
|
}
|
|
|
|
// Update the module context display
|
|
if (aiLogsDetails) {
|
|
const details = aiLogsDetails.innerHTML;
|
|
const updatedDetails = details.replace(
|
|
/Module: [^<]*/g,
|
|
`Module: ${module}`
|
|
).replace(
|
|
/Submodule: [^<]*/g,
|
|
`Submodule: ${submodule || 'NONE'}`
|
|
);
|
|
aiLogsDetails.innerHTML = updatedDetails;
|
|
}
|
|
}
|
|
|
|
// Update AI stats in real-time
|
|
function updateAIStats(logs) {
|
|
const logCount = logs.length;
|
|
let errorCount = 0;
|
|
let successCount = 0;
|
|
let infoCount = 0;
|
|
|
|
logs.forEach(log => {
|
|
if (log.status === 'error') errorCount++;
|
|
else if (log.status === 'success') successCount++;
|
|
else infoCount++;
|
|
});
|
|
|
|
// Update module context dynamically
|
|
updateModuleContext();
|
|
|
|
// Update message
|
|
if (aiLogsMessage) {
|
|
if (logCount > 0) {
|
|
aiLogsMessage.textContent = `AI Logs: ${logCount} events (${successCount} success, ${errorCount} errors)`;
|
|
} else {
|
|
aiLogsMessage.textContent = 'AI Logs: Ready (no events yet)';
|
|
}
|
|
}
|
|
|
|
// Update details
|
|
if (aiLogsDetails) {
|
|
const aiMode = 'AI Mode: ✅ Enabled'; // This could be dynamic
|
|
const events = `Recent Events: ${logCount}`;
|
|
const stats = `Success: ${successCount} | Errors: ${errorCount}`;
|
|
const module = `Module: planner`; // This could be dynamic
|
|
const submodule = `Submodule: keywords`; // This could be dynamic
|
|
|
|
aiLogsDetails.innerHTML = `${aiMode}<br>${events}<br>${stats}<br>${module}<br>${submodule}`;
|
|
}
|
|
}
|
|
|
|
// Display AI events in real-time
|
|
function displayAIEvents(logs) {
|
|
if (!logs || logs.length === 0) {
|
|
aiLogsEvents.innerHTML = '<div style="text-align: center; color: #666; padding: 10px;">No AI events logged yet</div>';
|
|
return;
|
|
}
|
|
|
|
// Get current page context
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const currentPage = urlParams.get('page') || '';
|
|
const currentSubmodule = urlParams.get('sm') || '';
|
|
|
|
// Filter logs based on current page
|
|
let filteredLogs = logs;
|
|
|
|
// On writer tasks page, only show content_generation logs
|
|
if (currentPage === 'igny8-writer' && currentSubmodule === 'tasks') {
|
|
filteredLogs = logs.filter(log => log.action === 'content_generation');
|
|
}
|
|
// On writer drafts page, only show image generation logs (handled by separate card)
|
|
else if (currentPage === 'igny8-writer' && currentSubmodule === 'drafts') {
|
|
filteredLogs = logs.filter(log => log.action !== 'content_generation');
|
|
}
|
|
|
|
if (filteredLogs.length === 0) {
|
|
aiLogsEvents.innerHTML = '<div style="text-align: center; color: #666; padding: 10px;">No relevant AI events for this page</div>';
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
filteredLogs.slice(0, 20).forEach((log, index) => {
|
|
const timestamp = new Date(log.timestamp).toLocaleTimeString();
|
|
const statusColor = log.status === 'success' ? '#28a745' : log.status === 'error' ? '#dc3545' : '#ffc107';
|
|
const statusBg = log.status === 'success' ? '#d4edda' : log.status === 'error' ? '#f8d7da' : '#fff3cd';
|
|
const statusIcon = log.status === 'success' ? '✓' : log.status === 'error' ? '✗' : 'ⓘ';
|
|
|
|
html += `
|
|
<div style="background: ${statusBg}; border-left: 4px solid ${statusColor}; padding: 12px; margin-bottom: 10px; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
|
|
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
|
|
<span style="color: ${statusColor}; font-weight: bold; font-size: 16px;">${statusIcon}</span>
|
|
<strong style="font-size: 13px; flex: 1;">${log.event}</strong>
|
|
<span style="font-size: 11px; color: #666;">${timestamp}</span>
|
|
</div>
|
|
<div style="font-size: 11px; color: #666; margin-left: 24px; margin-bottom: 4px;">
|
|
<strong>Module:</strong> ${log.module} | <strong>Action:</strong> ${log.action}
|
|
</div>
|
|
${log.message ? `<div style="font-size: 12px; color: #333; margin-left: 24px; padding: 6px; background: rgba(255,255,255,0.5); border-radius: 3px;">${log.message}</div>` : ''}
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
aiLogsEvents.innerHTML = html;
|
|
}
|
|
|
|
// Event listeners
|
|
if (refreshBtn) {
|
|
refreshBtn.addEventListener('click', function() {
|
|
loadAILogs();
|
|
});
|
|
}
|
|
|
|
if (clearBtn) {
|
|
clearBtn.addEventListener('click', function() {
|
|
if (confirm('Are you sure you want to clear all AI logs?')) {
|
|
const ajaxUrl = window.ajaxurl || (window.IGNY8_PAGE?.ajaxUrl) || '/wp-admin/admin-ajax.php';
|
|
fetch(ajaxUrl, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: 'action=igny8_clear_ai_logs&nonce=' + (window.IGNY8_PAGE?.nonce || '')
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
loadAILogs();
|
|
}
|
|
})
|
|
.catch(error => console.error('Error clearing AI logs:', error));
|
|
}
|
|
});
|
|
}
|
|
|
|
// Initial load
|
|
updateModuleContext(); // Update context first
|
|
loadAILogs();
|
|
|
|
// Auto-refresh every 5 seconds for real-time updates
|
|
refreshInterval = setInterval(loadAILogs, 5000);
|
|
|
|
// Clean up interval when page unloads
|
|
window.addEventListener('beforeunload', function() {
|
|
if (refreshInterval) {
|
|
clearInterval(refreshInterval);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Image Generation Debug functionality (only on drafts page)
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const imageGenMessage = document.getElementById('image-gen-message');
|
|
const imageGenDetails = document.getElementById('image-gen-details');
|
|
const imageGenEvents = document.getElementById('image-gen-events');
|
|
const refreshImageGenBtn = document.getElementById('refresh-image-gen');
|
|
const clearImageGenBtn = document.getElementById('clear-image-gen');
|
|
|
|
// Only initialize if we're on drafts page (image-gen-events element exists)
|
|
if (!imageGenEvents) return;
|
|
|
|
let imageGenRefreshInterval;
|
|
|
|
// Load image generation events
|
|
function loadImageGenEvents() {
|
|
// For now, just show placeholder - will be implemented later
|
|
if (imageGenMessage) {
|
|
imageGenMessage.textContent = 'Image generation debug ready';
|
|
}
|
|
if (imageGenDetails) {
|
|
imageGenDetails.textContent = 'Status: Waiting for image generation process';
|
|
}
|
|
}
|
|
|
|
// Add image generation debug log function (exposed globally)
|
|
window.addImageGenDebugLog = function(level, message, data = null) {
|
|
const timestamp = new Date().toLocaleTimeString();
|
|
const statusColor = level === 'SUCCESS' ? '#28a745' : level === 'ERROR' ? '#dc3545' : '#0073aa';
|
|
const statusIcon = level === 'SUCCESS' ? '✓' : level === 'ERROR' ? '✗' : 'ⓘ';
|
|
|
|
// Build data display
|
|
let dataDisplay = '';
|
|
if (data) {
|
|
if (data.postTitle) {
|
|
dataDisplay = `Post: ${data.postTitle} (ID: ${data.postId})`;
|
|
} else if (data.attachmentId) {
|
|
dataDisplay = `Attachment ID: ${data.attachmentId}, Provider: ${data.provider || 'N/A'}`;
|
|
} else if (data.queuePosition) {
|
|
dataDisplay = `Queue Position: ${data.queuePosition}`;
|
|
if (data.type) dataDisplay += `, Type: ${data.type}`;
|
|
if (data.device && data.device !== 'N/A') dataDisplay += `, Device: ${data.device}`;
|
|
if (data.index) dataDisplay += `, Index: ${data.index}`;
|
|
if (data.postId) dataDisplay += `, Task ID: ${data.postId}`;
|
|
} else if (data.postIds) {
|
|
dataDisplay = `Task IDs: ${Array.isArray(data.postIds) ? data.postIds.join(', ') : data.postIds}`;
|
|
} else if (data.taskIds) {
|
|
dataDisplay = `Task IDs: ${Array.isArray(data.taskIds) ? data.taskIds.join(', ') : data.taskIds}`;
|
|
} else if (data.mapping) {
|
|
const mappings = Object.entries(data.mapping).map(([taskId, postId]) => `Task ${taskId} → Post ${postId}`);
|
|
dataDisplay = mappings.join(', ');
|
|
} else if (data.desktop !== undefined) {
|
|
dataDisplay = `Desktop: ${data.desktop ? 'Yes' : 'No'}, Mobile: ${data.mobile ? 'Yes' : 'No'}, Max: ${data.maxImages}`;
|
|
} else if (data.status) {
|
|
dataDisplay = `Status: ${data.status}`;
|
|
} else if (data.error) {
|
|
dataDisplay = `Error: ${data.error}`;
|
|
} else if (data.total) {
|
|
dataDisplay = `Total Images: ${data.total}`;
|
|
} else if (data.postId) {
|
|
dataDisplay = `Task ID: ${data.postId}`;
|
|
}
|
|
}
|
|
|
|
const statusBg = level === 'SUCCESS' ? '#d4edda' : level === 'ERROR' ? '#f8d7da' : '#e7f3ff';
|
|
|
|
const eventHtml = `
|
|
<div style="background: ${statusBg}; border-left: 4px solid ${statusColor}; padding: 12px; margin-bottom: 10px; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
|
|
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
|
|
<span style="color: ${statusColor}; font-weight: bold; font-size: 16px;">${statusIcon}</span>
|
|
<strong style="font-size: 13px; flex: 1;">${message}</strong>
|
|
<span style="font-size: 11px; color: #666;">${timestamp}</span>
|
|
</div>
|
|
<div style="font-size: 11px; color: #666; margin-left: 24px; margin-bottom: 4px;">
|
|
<strong>Type:</strong> Image Generation | <strong>Level:</strong> ${level}
|
|
</div>
|
|
${dataDisplay ? `<div style="font-size: 12px; color: #333; margin-left: 24px; padding: 6px; background: rgba(255,255,255,0.5); border-radius: 3px;">${dataDisplay}</div>` : ''}
|
|
</div>
|
|
`;
|
|
|
|
// Add to top of events container (prepend new logs)
|
|
const currentContent = imageGenEvents.innerHTML;
|
|
if (currentContent.includes('No image generation events yet')) {
|
|
imageGenEvents.innerHTML = eventHtml;
|
|
} else {
|
|
imageGenEvents.innerHTML = eventHtml + currentContent;
|
|
}
|
|
|
|
// Update message
|
|
if (imageGenMessage) {
|
|
imageGenMessage.textContent = 'Image generation in progress';
|
|
}
|
|
if (imageGenDetails) {
|
|
imageGenDetails.textContent = 'Status: Processing image generation events';
|
|
}
|
|
};
|
|
|
|
// Display image generation events
|
|
function displayImageGenEvents(events) {
|
|
if (!events || events.length === 0) {
|
|
imageGenEvents.innerHTML = '<div style="text-align: center; color: #666; padding: 10px;"><span class="dashicons dashicons-format-image" style="font-size: 16px;"></span><br>No image generation events yet</div>';
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
events.slice(0, 10).forEach(event => {
|
|
const timestamp = new Date(event.timestamp).toLocaleTimeString();
|
|
const statusColor = event.status === 'success' ? '#28a745' : event.status === 'error' ? '#dc3545' : '#ffc107';
|
|
const statusIcon = event.status === 'success' ? '✓' : event.status === 'error' ? '✗' : 'ⓘ';
|
|
|
|
html += `
|
|
<div style="border-bottom: 1px solid #ddd; padding: 4px 0; margin-bottom: 4px;">
|
|
<div style="display: flex; align-items: center; gap: 6px; margin-bottom: 2px;">
|
|
<span style="color: ${statusColor}; font-weight: bold;">${statusIcon}</span>
|
|
<strong style="font-size: 11px;">${event.event}</strong>
|
|
<span style="font-size: 9px; color: #666; margin-left: auto;">${timestamp}</span>
|
|
</div>
|
|
<div style="font-size: 9px; color: #666; margin-left: 16px;">
|
|
${event.module} | ${event.action}
|
|
</div>
|
|
${event.message ? `<div style="font-size: 9px; color: #333; margin-left: 16px; margin-top: 2px;">${event.message}</div>` : ''}
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
imageGenEvents.innerHTML = html;
|
|
}
|
|
|
|
// Event listeners
|
|
if (refreshImageGenBtn) {
|
|
refreshImageGenBtn.addEventListener('click', function() {
|
|
loadImageGenEvents();
|
|
});
|
|
}
|
|
|
|
if (clearImageGenBtn) {
|
|
clearImageGenBtn.addEventListener('click', function() {
|
|
if (confirm('Are you sure you want to clear all image generation events?')) {
|
|
imageGenEvents.innerHTML = '<div style="text-align: center; color: #666; padding: 10px;"><span class="dashicons dashicons-format-image" style="font-size: 16px;"></span><br>No image generation events yet</div>';
|
|
}
|
|
});
|
|
}
|
|
|
|
// Initial load
|
|
loadImageGenEvents();
|
|
|
|
// Auto-refresh every 3 seconds for image generation events
|
|
imageGenRefreshInterval = setInterval(loadImageGenEvents, 3000);
|
|
|
|
// Clean up interval when page unloads
|
|
window.addEventListener('beforeunload', function() {
|
|
if (imageGenRefreshInterval) {
|
|
clearInterval(imageGenRefreshInterval);
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<?php
|
|
return ob_get_clean();
|
|
}
|
|
|
|
// Keep the old function for backward compatibility
|
|
function igny8_render_module_debug_widgets() {
|
|
echo igny8_get_module_debug_content();
|
|
}
|