reference plugin and image gen analysis
This commit is contained in:
14
igny8-ai-seo-wp-plugin/modules/components/_README.php
Normal file
14
igny8-ai-seo-wp-plugin/modules/components/_README.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
/**
|
||||
* ==============================
|
||||
* 📁 Folder Scope Declaration
|
||||
* ==============================
|
||||
* Folder: /components/
|
||||
* Purpose: UI/UX templates (forms, modals, tables)
|
||||
* Rules:
|
||||
* - Can be reused globally across all modules
|
||||
* - Contains only UI template components
|
||||
* - No business logic allowed
|
||||
* - Must be configuration-driven
|
||||
* - Pure presentation layer only
|
||||
*/
|
||||
111
igny8-ai-seo-wp-plugin/modules/components/actions-tpl.php
Normal file
111
igny8-ai-seo-wp-plugin/modules/components/actions-tpl.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
/**
|
||||
* ==========================
|
||||
* 🔐 IGNY8 FILE RULE HEADER
|
||||
* ==========================
|
||||
* @file : actions-tpl.php
|
||||
* @location : /modules/components/actions-tpl.php
|
||||
* @type : Component
|
||||
* @scope : Cross-Module
|
||||
* @allowed : Action rendering, bulk operations
|
||||
* @reusability : Shared
|
||||
* @notes : Dynamic action component for all modules
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Render table actions function
|
||||
function igny8_render_table_actions($table_id, $actions = []) {
|
||||
|
||||
// Set default actions if none provided
|
||||
$actions = $actions ?: ['export_selected', 'delete_selected', 'export_all', 'import', 'add_new'];
|
||||
|
||||
// Check if AI mode is enabled
|
||||
$ai_mode = igny8_get_ai_setting('planner_mode', 'manual') === 'ai';
|
||||
|
||||
// Start output buffering to capture HTML
|
||||
ob_start();
|
||||
?>
|
||||
<!-- Table Actions -->
|
||||
<div class="igny8-table-actions" data-table="<?php echo esc_attr($table_id); ?>">
|
||||
<div class="left-actions">
|
||||
<span id="<?php echo esc_attr($table_id); ?>_count" class="igny8-count-hidden">0 selected</span>
|
||||
<?php if (in_array('export_selected', $actions)): ?>
|
||||
<button id="<?php echo esc_attr($table_id); ?>_export_btn" class="igny8-btn igny8-btn-success" disabled>Export Selected</button>
|
||||
<?php endif; ?>
|
||||
<?php if (in_array('delete_selected', $actions)): ?>
|
||||
<button id="<?php echo esc_attr($table_id); ?>_delete_btn" class="igny8-btn igny8-btn-danger" disabled>Delete Selected</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Publish Selected Button (for Drafts) -->
|
||||
<?php if ($table_id === 'writer_drafts'): ?>
|
||||
<button id="<?php echo esc_attr($table_id); ?>_generate_images_btn" class="igny8-btn igny8-btn-accent" disabled>
|
||||
<span class="dashicons dashicons-format-image"></span> Generate Images
|
||||
</button>
|
||||
<button id="<?php echo esc_attr($table_id); ?>_publish_btn" class="igny8-btn igny8-btn-primary" disabled>
|
||||
<span class="dashicons dashicons-yes-alt"></span> Publish Selected
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- AI Action Buttons (only visible in AI mode) -->
|
||||
<?php if ($ai_mode): ?>
|
||||
<?php if ($table_id === 'planner_keywords' && igny8_get_ai_setting('clustering', 'enabled') === 'enabled'): ?>
|
||||
<button id="<?php echo esc_attr($table_id); ?>_ai_cluster_btn" class="igny8-btn igny8-btn-accent" disabled>
|
||||
<span class="dashicons dashicons-admin-generic"></span> Auto Cluster
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($table_id === 'planner_clusters' && igny8_get_ai_setting('ideas', 'enabled') === 'enabled'): ?>
|
||||
<button id="<?php echo esc_attr($table_id); ?>_ai_ideas_btn" class="igny8-btn igny8-btn-accent" disabled>
|
||||
<span class="dashicons dashicons-lightbulb"></span> Generate Ideas
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Queue to Writer Button (for Ideas) -->
|
||||
<?php if ($table_id === 'planner_ideas'): ?>
|
||||
<button id="<?php echo esc_attr($table_id); ?>_queue_writer_btn" class="igny8-btn igny8-btn-primary" disabled>
|
||||
<span class="dashicons dashicons-edit"></span> Queue to Writer
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($table_id === 'writer_tasks' && igny8_get_ai_setting('writer_mode', 'manual') === 'ai' && igny8_get_ai_setting('content_generation', 'enabled') === 'enabled'): ?>
|
||||
<button id="<?php echo esc_attr($table_id); ?>_generate_content_btn" class="igny8-btn igny8-btn-success" disabled>
|
||||
<span class="dashicons dashicons-admin-generic"></span> Generate Content
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="right-actions">
|
||||
<div class="igny8-ml-auto igny8-flex igny8-flex-gap-10 igny8-align-center">
|
||||
<?php if (in_array('export_all', $actions)): ?>
|
||||
<button id="<?php echo esc_attr($table_id); ?>_export_all_btn" class="igny8-btn igny8-btn-outline" onclick="igny8ShowExportModal('<?php echo esc_attr($table_id); ?>')">Export All</button>
|
||||
<?php endif; ?>
|
||||
<?php if (in_array('import', $actions)): ?>
|
||||
<button id="<?php echo esc_attr($table_id); ?>_import_btn" class="igny8-btn igny8-btn-secondary">Import</button>
|
||||
<?php endif; ?>
|
||||
<?php if (in_array('add_new', $actions)): ?>
|
||||
<button
|
||||
class="igny8-btn igny8-btn-primary"
|
||||
data-action="addRow"
|
||||
data-table-id="<?php echo esc_attr($table_id); ?>"
|
||||
>
|
||||
Add New
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Global notification system is handled by core.js -->
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
// Set default values
|
||||
$table_id = $table_id ?? 'data_table';
|
||||
$actions = $actions ?? ['export_selected', 'delete_selected', 'export_all', 'import', 'add_new'];
|
||||
?>
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/**
|
||||
* ==========================
|
||||
* 🔐 IGNY8 FILE RULE HEADER
|
||||
* ==========================
|
||||
* @file : export-modal-tpl.php
|
||||
* @location : /modules/components/export-modal-tpl.php
|
||||
* @type : Component
|
||||
* @scope : Cross-Module
|
||||
* @allowed : Modal rendering, export functionality
|
||||
* @reusability : Shared
|
||||
* @notes : Dynamic export modal component for all modules
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH') && !defined('IGNY8_INCLUDE_TEMPLATE')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Get configuration for this table
|
||||
$config = igny8_get_import_export_config($table_id);
|
||||
if (!$config) {
|
||||
return;
|
||||
}
|
||||
|
||||
$singular = $config['singular'];
|
||||
$plural = $config['plural'];
|
||||
$is_selected = !empty($selected_ids);
|
||||
$mode_text = $is_selected ? "Selected {$plural}" : "All {$plural}";
|
||||
?>
|
||||
|
||||
<div id="igny8-import-export-modal" class="igny8-modal" data-table-id="<?php echo esc_attr($table_id); ?>">
|
||||
<div class="igny8-modal-content">
|
||||
<div class="igny8-modal-header">
|
||||
<h3>Export <?php echo esc_html($mode_text); ?></h3>
|
||||
<button class="igny8-btn-close" onclick="igny8CloseImportExportModal()">×</button>
|
||||
</div>
|
||||
<div class="igny8-modal-body">
|
||||
<p>Export <?php echo esc_html(strtolower($mode_text)); ?> to CSV format.</p>
|
||||
|
||||
<!-- Export Options -->
|
||||
<div class="igny8-form-group">
|
||||
<h4>Export Options</h4>
|
||||
<div class="igny8-checkbox-group">
|
||||
<label class="igny8-checkbox-label">
|
||||
<input type="checkbox" id="include-metrics" name="include_metrics" checked>
|
||||
<span class="igny8-checkbox-text">Include Metrics</span>
|
||||
</label>
|
||||
<label class="igny8-checkbox-label">
|
||||
<input type="checkbox" id="include-relationships" name="include_relationships">
|
||||
<span class="igny8-checkbox-text">Include Relationships</span>
|
||||
</label>
|
||||
<label class="igny8-checkbox-label">
|
||||
<input type="checkbox" id="include-timestamps" name="include_timestamps">
|
||||
<span class="igny8-checkbox-text">Include Timestamps</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden form data -->
|
||||
<form id="igny8-modal-export-form" style="display: none;">
|
||||
<input type="hidden" name="action" value="<?php echo $is_selected ? 'igny8_export_selected' : 'igny8_run_export'; ?>">
|
||||
<input type="hidden" name="nonce" value="">
|
||||
<input type="hidden" name="export_type" value="<?php echo esc_attr($config['type']); ?>">
|
||||
<?php if ($is_selected): ?>
|
||||
<input type="hidden" name="selected_ids" value="<?php echo esc_attr(json_encode($selected_ids)); ?>">
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
<div class="igny8-modal-footer">
|
||||
<button type="button" class="igny8-btn igny8-btn-secondary" onclick="igny8CloseImportExportModal()">Cancel</button>
|
||||
<button type="button" class="igny8-btn igny8-btn-primary" onclick="igny8SubmitExportForm()">
|
||||
<span class="dashicons dashicons-download"></span> Export <?php echo esc_html($mode_text); ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
136
igny8-ai-seo-wp-plugin/modules/components/filters-tpl.php
Normal file
136
igny8-ai-seo-wp-plugin/modules/components/filters-tpl.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/**
|
||||
* ==========================
|
||||
* 🔐 IGNY8 FILE RULE HEADER
|
||||
* ==========================
|
||||
* @file : filters-tpl.php
|
||||
* @location : /modules/components/filters-tpl.php
|
||||
* @type : Component
|
||||
* @scope : Cross-Module
|
||||
* @allowed : Filter rendering, search functionality
|
||||
* @reusability : Shared
|
||||
* @notes : Dynamic filter component for all modules
|
||||
*/
|
||||
/*
|
||||
* <?php igny8_render_filters('planner_clusters'); ?>
|
||||
* <?php igny8_render_filters('writer_drafts'); ?>
|
||||
*
|
||||
* @package Igny8Compact
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Render filters function
|
||||
function igny8_render_filters($table_id) {
|
||||
// Load filters configuration
|
||||
$filters_config = require plugin_dir_path(__FILE__) . '../config/filters-config.php';
|
||||
$filters = $filters_config[$table_id] ?? [];
|
||||
|
||||
// Load table configuration to get humanize_columns
|
||||
$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);
|
||||
$humanize_columns = $table_config['humanize_columns'] ?? [];
|
||||
|
||||
// Set variables for component
|
||||
$module = explode('_', $table_id)[0];
|
||||
$tab = explode('_', $table_id)[1] ?? '';
|
||||
|
||||
// Debug: Log filters array for verification
|
||||
|
||||
// Start output buffering to capture HTML
|
||||
ob_start();
|
||||
?>
|
||||
<!-- Filters HTML -->
|
||||
<div class="igny8-filters igny8-mb-20" data-table="<?php echo esc_attr($table_id); ?>">
|
||||
<div class="igny8-filter-bar">
|
||||
<?php foreach ($filters as $filter_key => $filter_config): ?>
|
||||
<div class="igny8-filter-group">
|
||||
<?php if ($filter_config['type'] === 'search'): ?>
|
||||
<!-- Search Input -->
|
||||
<input type="text"
|
||||
class="igny8-search-input igny8-input-md"
|
||||
id="<?php echo esc_attr($table_id); ?>_filter_<?php echo esc_attr($filter_key); ?>"
|
||||
placeholder="<?php echo esc_attr($filter_config['placeholder'] ?? 'Search...'); ?>"
|
||||
data-filter="<?php echo esc_attr($filter_key); ?>">
|
||||
<?php elseif ($filter_config['type'] === 'select'): ?>
|
||||
<!-- Dropdown Filter -->
|
||||
<div class="select" data-filter="<?php echo esc_attr($filter_key); ?>">
|
||||
<button class="select-btn" id="<?php echo esc_attr($table_id); ?>_filter_<?php echo esc_attr($filter_key); ?>_btn" data-value="">
|
||||
<span class="select-text"><?php echo esc_html($filter_config['label'] ?? (in_array($filter_config['field'] ?? $filter_key, $humanize_columns) ? igny8_humanize_label($filter_config['field'] ?? $filter_key) : ucfirst($filter_key))); ?></span>
|
||||
<span class="dd-arrow">▼</span>
|
||||
</button>
|
||||
<div class="select-list" id="<?php echo esc_attr($table_id); ?>_filter_<?php echo esc_attr($filter_key); ?>_list">
|
||||
<div class="select-item" data-value="">All <?php echo esc_html($filter_config['label'] ?? (in_array($filter_config['field'] ?? $filter_key, $humanize_columns) ? igny8_humanize_label($filter_config['field'] ?? $filter_key) : ucfirst($filter_key))); ?></div>
|
||||
<?php if (isset($filter_config['options'])): ?>
|
||||
<?php if (is_array($filter_config['options'])): ?>
|
||||
<?php foreach ($filter_config['options'] as $value => $label): ?>
|
||||
<div class="select-item" data-value="<?php echo esc_attr($value); ?>"><?php echo esc_html($label); ?></div>
|
||||
<?php endforeach; ?>
|
||||
<?php elseif ($filter_config['options'] === 'dynamic_clusters'): ?>
|
||||
<?php
|
||||
// Load cluster options dynamically
|
||||
$cluster_options = igny8_get_cluster_options();
|
||||
if ($cluster_options) {
|
||||
foreach ($cluster_options as $option) {
|
||||
if (!empty($option['value'])) { // Skip "No Cluster" option
|
||||
echo '<div class="select-item" data-value="' . esc_attr($option['value']) . '">' . esc_html($option['label']) . '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php elseif ($filter_config['type'] === 'range'): ?>
|
||||
<!-- Numeric Range Filter -->
|
||||
<div class="select" data-filter="<?php echo esc_attr($filter_key); ?>">
|
||||
<button class="select-btn" id="<?php echo esc_attr($table_id); ?>_filter_<?php echo esc_attr($filter_key); ?>_btn" data-value="">
|
||||
<span class="select-text"><?php echo esc_html($filter_config['label'] ?? (in_array($filter_config['field'] ?? $filter_key, $humanize_columns) ? igny8_humanize_label($filter_config['field'] ?? $filter_key) : ucfirst($filter_key))); ?></span>
|
||||
<span>▼</span>
|
||||
</button>
|
||||
<div class="select-list igny8-dropdown-panel" id="<?php echo esc_attr($table_id); ?>_filter_<?php echo esc_attr($filter_key); ?>_list">
|
||||
<div style="margin-bottom: 10px;">
|
||||
<label class="igny8-text-sm igny8-mb-5 igny8-text-muted">Min <?php echo esc_html($filter_config['label'] ?? (in_array($filter_config['field'] ?? $filter_key, $humanize_columns) ? igny8_humanize_label($filter_config['field'] ?? $filter_key) : ucfirst($filter_key))); ?>:</label>
|
||||
<input type="number" id="<?php echo esc_attr($table_id); ?>_filter_<?php echo esc_attr($filter_key); ?>_min" placeholder="0" class="igny8-input-sm">
|
||||
</div>
|
||||
<div style="margin-bottom: 10px;">
|
||||
<label class="igny8-text-sm igny8-mb-5 igny8-text-muted">Max <?php echo esc_html($filter_config['label'] ?? (in_array($filter_config['field'] ?? $filter_key, $humanize_columns) ? igny8_humanize_label($filter_config['field'] ?? $filter_key) : ucfirst($filter_key))); ?>:</label>
|
||||
<input type="number" id="<?php echo esc_attr($table_id); ?>_filter_<?php echo esc_attr($filter_key); ?>_max" placeholder="10000" class="igny8-input-sm">
|
||||
</div>
|
||||
<div class="igny8-flex igny8-flex-gap-10">
|
||||
<button type="button" id="<?php echo esc_attr($table_id); ?>_filter_<?php echo esc_attr($filter_key); ?>_ok" class="igny8-btn igny8-btn-primary igny8-flex igny8-p-5 igny8-text-xs">Ok</button>
|
||||
<button type="button" id="<?php echo esc_attr($table_id); ?>_filter_<?php echo esc_attr($filter_key); ?>_clear" class="igny8-btn igny8-btn-secondary igny8-flex igny8-p-5 igny8-text-xs">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<div class="igny8-filter-actions">
|
||||
<button class="igny8-btn igny8-btn-primary" id="<?php echo esc_attr($table_id); ?>_filter_apply_btn">Apply Filters</button>
|
||||
<button class="igny8-btn igny8-btn-secondary" id="<?php echo esc_attr($table_id); ?>_filter_reset_btn">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
// Set default values
|
||||
$table_id = $table_id ?? 'data_table';
|
||||
$filters = $filters ?? [];
|
||||
$module = $module ?? '';
|
||||
$tab = $tab ?? '';
|
||||
|
||||
// Debug state: Filter HTML rendered
|
||||
if (function_exists('igny8_debug_state')) {
|
||||
igny8_debug_state('FILTER_HTML_RENDERED', true, 'Filter HTML rendered for ' . $table_id);
|
||||
}
|
||||
?>
|
||||
176
igny8-ai-seo-wp-plugin/modules/components/forms-tpl.php
Normal file
176
igny8-ai-seo-wp-plugin/modules/components/forms-tpl.php
Normal file
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
/**
|
||||
* ==========================
|
||||
* 🔐 IGNY8 FILE RULE HEADER
|
||||
* ==========================
|
||||
* @file : forms-tpl.php
|
||||
* @location : /modules/components/forms-tpl.php
|
||||
* @type : Component
|
||||
* @scope : Cross-Module
|
||||
* @allowed : Form rendering, validation, data processing
|
||||
* @reusability : Shared
|
||||
* @notes : Dynamic form component for all modules
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
function igny8_render_inline_form_row($table_id, $mode = 'add', $record_data = []) {
|
||||
$form_config = igny8_get_form_config($table_id);
|
||||
|
||||
if (!$form_config) {
|
||||
return '<tr><td colspan="100%">Form not configured for table: ' . esc_html($table_id) . '</td></tr>';
|
||||
}
|
||||
|
||||
$row_id_attr = ($mode === 'edit' && !empty($record_data['id']))
|
||||
? ' data-id="' . esc_attr($record_data['id']) . '"'
|
||||
: '';
|
||||
|
||||
ob_start(); ?>
|
||||
|
||||
<tr class="igny8-inline-form-row" data-mode="<?php echo esc_attr($mode); ?>"<?php echo $row_id_attr; ?>>
|
||||
<td><input type="checkbox" disabled></td>
|
||||
|
||||
<?php foreach ($form_config['fields'] as $field): ?>
|
||||
<td>
|
||||
<?php echo igny8_render_form_field($field, $record_data, $mode); ?>
|
||||
</td>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<td class="igny8-align-center igny8-actions">
|
||||
<button
|
||||
type="button"
|
||||
class="igny8-btn igny8-btn-success igny8-btn-sm igny8-form-save"
|
||||
data-table-id="<?php echo esc_attr($table_id); ?>"
|
||||
data-nonce="<?php echo esc_attr(wp_create_nonce('igny8_ajax_nonce')); ?>"
|
||||
title="Save"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 26 26" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="igny8-btn igny8-btn-secondary igny8-btn-sm igny8-form-cancel"
|
||||
title="Cancel"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 26 26" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="5" y1="5" x2="20" y2="20"></line>
|
||||
<line x1="5" y1="20" x2="20" y2="5"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<style>
|
||||
tr.igny8-inline-form-row {
|
||||
animation: slideInForm 0.3s ease-out;
|
||||
}
|
||||
@keyframes slideInForm {
|
||||
from { opacity: 0; transform: translateY(-10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
tr.igny8-inline-form-row td {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
tr.igny8-inline-form-row:hover td {
|
||||
background-color: rgba(0, 123, 255, 0.05);
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
function igny8_render_form_field($field, $record_data = [], $mode = 'add') {
|
||||
$field_name = $field['name'];
|
||||
$field_type = $field['type'];
|
||||
$field_label = $field['label'] ?? ucfirst($field_name);
|
||||
$field_value = isset($record_data[$field_name]) ? $record_data[$field_name] : ($field['default'] ?? '');
|
||||
$is_required = !empty($field['required']);
|
||||
|
||||
switch ($field_type) {
|
||||
case 'number':
|
||||
return igny8_render_number_field($field_name, $field_label, $field_value, $is_required, $field['step'] ?? '');
|
||||
case 'select':
|
||||
return igny8_render_select_field($field_name, $field_label, $field_value, $field, $is_required);
|
||||
case 'textarea':
|
||||
return igny8_render_textarea_field($field_name, $field_label, $field_value, $is_required);
|
||||
default:
|
||||
return igny8_render_text_field($field_name, $field_label, $field_value, $is_required);
|
||||
}
|
||||
}
|
||||
|
||||
function igny8_render_text_field($name, $label, $value, $required = false) {
|
||||
ob_start();
|
||||
?>
|
||||
<input type="text" name="<?php echo esc_attr($name); ?>" placeholder="<?php echo esc_attr($label); ?>"
|
||||
class="igny8-input-sm" value="<?php echo esc_attr($value); ?>"<?php echo $required ? ' required' : ''; ?> />
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
function igny8_render_number_field($name, $label, $value, $required = false, $step = '') {
|
||||
ob_start();
|
||||
?>
|
||||
<input type="number" name="<?php echo esc_attr($name); ?>" placeholder="<?php echo esc_attr($label); ?>"
|
||||
class="igny8-input-sm" value="<?php echo esc_attr($value); ?>"<?php
|
||||
echo $step ? ' step="' . esc_attr($step) . '"' : '';
|
||||
echo $required ? ' required' : ''; ?> />
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
function igny8_render_select_field($name, $label, $value, $field_config, $required = false) {
|
||||
$display_text = 'Select ' . esc_html($label);
|
||||
$options = [];
|
||||
|
||||
// Get options
|
||||
if (isset($field_config['source']) && function_exists($field_config['source'])) {
|
||||
try {
|
||||
$dynamic_options = call_user_func($field_config['source']);
|
||||
foreach ($dynamic_options as $option) {
|
||||
$val = $option['value'] ?? $option;
|
||||
$lbl = $option['label'] ?? $option;
|
||||
$options[$val] = $lbl;
|
||||
if ($value == $val) $display_text = esc_html($lbl);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$options = [];
|
||||
}
|
||||
} else {
|
||||
$options = $field_config['options'] ?? [];
|
||||
foreach ($options as $val => $lbl) {
|
||||
if ($value == $val) $display_text = esc_html($lbl);
|
||||
}
|
||||
}
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="select">
|
||||
<button type="button" class="select-btn" name="<?php echo esc_attr($name); ?>" data-value="<?php echo esc_attr($value); ?>">
|
||||
<span class="select-text"><?php echo $display_text; ?></span>
|
||||
<span>▼</span>
|
||||
</button>
|
||||
<div class="select-list" style="max-height:200px;overflow-y:auto;">
|
||||
<div class="select-item" data-value="">Select <?php echo esc_html($label); ?></div>
|
||||
<?php foreach ($options as $val => $lbl): ?>
|
||||
<div class="select-item" data-value="<?php echo esc_attr($val); ?>"><?php echo esc_html($lbl); ?></div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
function igny8_render_textarea_field($name, $label, $value, $required = false) {
|
||||
ob_start();
|
||||
?>
|
||||
<textarea name="<?php echo esc_attr($name); ?>" placeholder="<?php echo esc_attr($label); ?>"
|
||||
class="igny8-input-sm" rows="3"<?php echo $required ? ' required' : ''; ?>><?php echo esc_textarea($value); ?></textarea>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/**
|
||||
* ==========================
|
||||
* 🔐 IGNY8 FILE RULE HEADER
|
||||
* ==========================
|
||||
* @file : import-modal-tpl.php
|
||||
* @location : /modules/components/import-modal-tpl.php
|
||||
* @type : Component
|
||||
* @scope : Cross-Module
|
||||
* @allowed : Modal rendering, import functionality
|
||||
* @reusability : Shared
|
||||
* @notes : Dynamic import modal component for all modules
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH') && !defined('IGNY8_INCLUDE_TEMPLATE')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Get configuration for this table
|
||||
$config = igny8_get_import_export_config($table_id);
|
||||
if (!$config) {
|
||||
return;
|
||||
}
|
||||
|
||||
$singular = $config['singular'];
|
||||
$plural = $config['plural'];
|
||||
?>
|
||||
|
||||
<div id="igny8-import-export-modal" class="igny8-modal" data-table-id="<?php echo esc_attr($table_id); ?>">
|
||||
<div class="igny8-modal-content">
|
||||
<div class="igny8-modal-header">
|
||||
<h3>Import <?php echo esc_html($plural); ?></h3>
|
||||
<button class="igny8-btn-close" onclick="igny8CloseImportExportModal()">×</button>
|
||||
</div>
|
||||
<div class="igny8-modal-body">
|
||||
<p>Import <?php echo esc_html(strtolower($plural)); ?> from a CSV file. Use the template below for proper format.</p>
|
||||
|
||||
<!-- Template Download -->
|
||||
<div class="igny8-mb-15">
|
||||
<button type="button" class="igny8-btn igny8-btn-outline" onclick="igny8DownloadTemplate('<?php echo esc_attr($table_id); ?>')">
|
||||
<span class="dashicons dashicons-download"></span> Download <?php echo esc_html($singular); ?> Template
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Import Form -->
|
||||
<form id="igny8-modal-import-form" enctype="multipart/form-data">
|
||||
<input type="hidden" name="action" value="igny8_run_import">
|
||||
<input type="hidden" name="nonce" value="">
|
||||
<input type="hidden" name="import_type" value="<?php echo esc_attr($config['type']); ?>">
|
||||
|
||||
<div class="igny8-form-group">
|
||||
<label for="import-file">Select CSV File</label>
|
||||
<input type="file" id="import-file" name="import_file" accept=".csv" required>
|
||||
<p class="description">Upload a CSV file with your <?php echo esc_html(strtolower($plural)); ?>. Use the template above for proper format.</p>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<div class="igny8-modal-footer">
|
||||
<button type="button" class="igny8-btn igny8-btn-secondary" onclick="igny8CloseImportExportModal()">Cancel</button>
|
||||
<button type="button" class="igny8-btn igny8-btn-success" onclick="igny8SubmitImportForm()">
|
||||
<span class="dashicons dashicons-upload"></span> Import <?php echo esc_html($plural); ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
140
igny8-ai-seo-wp-plugin/modules/components/kpi-tpl.php
Normal file
140
igny8-ai-seo-wp-plugin/modules/components/kpi-tpl.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
/**
|
||||
* ==========================
|
||||
* 🔐 IGNY8 FILE RULE HEADER
|
||||
* ==========================
|
||||
* @file : kpi-tpl.php
|
||||
* @location : /modules/components/kpi-tpl.php
|
||||
* @type : Component
|
||||
* @scope : Cross-Module
|
||||
* @allowed : KPI rendering, metrics display
|
||||
* @reusability : Shared
|
||||
* @notes : Dynamic KPI component for all modules
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get KPI data for a specific submodule
|
||||
*
|
||||
* @param string $table_id The table ID (e.g., 'planner_keywords')
|
||||
* @param array $kpi_config The KPI configuration array
|
||||
* @return array KPI data array
|
||||
*/
|
||||
function igny8_get_kpi_data($table_id, $kpi_config) {
|
||||
global $wpdb;
|
||||
|
||||
$kpi_data = [];
|
||||
|
||||
// Get table name from table_id
|
||||
$table_name = igny8_get_table_name($table_id);
|
||||
|
||||
if (empty($table_name) || empty($kpi_config)) {
|
||||
return $kpi_data;
|
||||
}
|
||||
|
||||
// Execute each KPI query
|
||||
foreach ($kpi_config as $kpi_key => $kpi_info) {
|
||||
if (!isset($kpi_info['query'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Replace placeholders with actual values
|
||||
$query = str_replace('{table_name}', $table_name, $kpi_info['query']);
|
||||
$query = str_replace('{prefix}', $wpdb->prefix, $query);
|
||||
|
||||
// Execute query safely
|
||||
$result = $wpdb->get_var($query);
|
||||
|
||||
// Store result (handle null/empty results)
|
||||
$kpi_data[$kpi_key] = $result !== null ? (int) $result : 0;
|
||||
}
|
||||
|
||||
return $kpi_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get actual table name from table ID
|
||||
*
|
||||
* @param string $table_id The table ID (e.g., 'planner_keywords')
|
||||
* @return string The actual table name (e.g., 'wp_igny8_keywords')
|
||||
*/
|
||||
function igny8_get_table_name($table_id) {
|
||||
global $wpdb;
|
||||
|
||||
// Map table IDs to actual table names
|
||||
$table_mapping = [
|
||||
'planner_home' => $wpdb->prefix . 'igny8_keywords', // Uses keywords table as base for home metrics
|
||||
'planner_keywords' => $wpdb->prefix . 'igny8_keywords',
|
||||
'planner_clusters' => $wpdb->prefix . 'igny8_clusters',
|
||||
'planner_ideas' => $wpdb->prefix . 'igny8_content_ideas',
|
||||
'writer_home' => $wpdb->prefix . 'igny8_content_ideas', // Uses ideas table as base for home metrics
|
||||
'writer_drafts' => $wpdb->prefix . 'igny8_tasks',
|
||||
'writer_published' => $wpdb->prefix . 'igny8_tasks',
|
||||
'writer_templates' => $wpdb->prefix . 'igny8_prompts',
|
||||
'writer_tasks' => $wpdb->prefix . 'igny8_tasks',
|
||||
'optimizer_audits' => $wpdb->prefix . 'igny8_logs',
|
||||
'optimizer_suggestions' => $wpdb->prefix . 'igny8_suggestions',
|
||||
'linker_backlinks' => $wpdb->prefix . 'igny8_backlinks',
|
||||
'linker_campaigns' => $wpdb->prefix . 'igny8_campaigns',
|
||||
'linker_links' => $wpdb->prefix . 'igny8_links',
|
||||
'personalize_rewrites' => $wpdb->prefix . 'igny8_variations',
|
||||
'personalize_tones' => $wpdb->prefix . 'igny8_sites',
|
||||
'personalize_data' => $wpdb->prefix . 'igny8_personalization',
|
||||
'personalize_variations' => $wpdb->prefix . 'igny8_variations',
|
||||
'personalize_records' => $wpdb->prefix . 'igny8_personalization',
|
||||
'thinker_prompts' => '' // No table needed for prompts submodule
|
||||
];
|
||||
|
||||
$table_name = $table_mapping[$table_id] ?? '';
|
||||
|
||||
// Throw error if unknown table ID (except for special cases that don't need tables)
|
||||
if (empty($table_name) && !in_array($table_id, ['thinker_prompts'])) {
|
||||
throw new InvalidArgumentException("Unknown table ID: {$table_id}");
|
||||
}
|
||||
|
||||
return $table_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a table exists in the database
|
||||
*
|
||||
* @param string $table_name The table name to check
|
||||
* @return bool True if table exists, false otherwise
|
||||
*/
|
||||
function igny8_table_exists($table_name) {
|
||||
global $wpdb;
|
||||
|
||||
$result = $wpdb->get_var($wpdb->prepare(
|
||||
"SHOW TABLES LIKE %s",
|
||||
$table_name
|
||||
));
|
||||
|
||||
return $result === $table_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get KPI data with fallback for missing tables
|
||||
*
|
||||
* @param string $table_id The table ID
|
||||
* @param array $kpi_config The KPI configuration
|
||||
* @return array KPI data array with fallback values
|
||||
*/
|
||||
function igny8_get_kpi_data_safe($table_id, $kpi_config) {
|
||||
$table_name = igny8_get_table_name($table_id);
|
||||
|
||||
// If table doesn't exist, return empty data
|
||||
if (!igny8_table_exists($table_name)) {
|
||||
$fallback_data = [];
|
||||
foreach ($kpi_config as $kpi_key => $kpi_info) {
|
||||
$fallback_data[$kpi_key] = 0;
|
||||
}
|
||||
return $fallback_data;
|
||||
}
|
||||
|
||||
// Get real data
|
||||
return igny8_get_kpi_data($table_id, $kpi_config);
|
||||
}
|
||||
136
igny8-ai-seo-wp-plugin/modules/components/pagination-tpl.php
Normal file
136
igny8-ai-seo-wp-plugin/modules/components/pagination-tpl.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/**
|
||||
* ==========================
|
||||
* 🔐 IGNY8 FILE RULE HEADER
|
||||
* ==========================
|
||||
* @file : pagination-tpl.php
|
||||
* @location : /modules/components/pagination-tpl.php
|
||||
* @type : Component
|
||||
* @scope : Cross-Module
|
||||
* @allowed : Pagination rendering, navigation controls
|
||||
* @reusability : Shared
|
||||
* @notes : Dynamic pagination component for all modules
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Render pagination function
|
||||
function igny8_render_pagination($table_id, $pagination = []) {
|
||||
// Set variables for component
|
||||
$module = explode('_', $table_id)[0];
|
||||
$tab = explode('_', $table_id)[1] ?? '';
|
||||
|
||||
// Set default pagination values
|
||||
$pagination = array_merge([
|
||||
'current_page' => 1,
|
||||
'total_pages' => 1,
|
||||
'per_page' => 10,
|
||||
'total_items' => 0
|
||||
], $pagination);
|
||||
|
||||
// Start output buffering to capture HTML
|
||||
ob_start();
|
||||
?>
|
||||
<!-- Pagination -->
|
||||
<div class="igny8-pagination igny8-mt-20"
|
||||
data-table="<?php echo esc_attr($table_id); ?>"
|
||||
data-current-page="<?php echo esc_attr($pagination['current_page']); ?>"
|
||||
data-per-page="<?php echo esc_attr($pagination['per_page']); ?>"
|
||||
data-total-items="<?php echo esc_attr($pagination['total_items']); ?>"
|
||||
data-module="<?php echo esc_attr($module); ?>"
|
||||
data-tab="<?php echo esc_attr($tab); ?>">
|
||||
|
||||
<button class="igny8-btn igny8-btn-sm igny8-btn-outline igny8-page-btn"
|
||||
data-page="<?php echo max(1, $pagination['current_page'] - 1); ?>"
|
||||
<?php echo $pagination['current_page'] <= 1 ? 'disabled' : ''; ?>>
|
||||
‹ Previous
|
||||
</button>
|
||||
|
||||
<?php if ($pagination['total_items'] > 20): ?>
|
||||
<?php
|
||||
$current = $pagination['current_page'];
|
||||
$total = $pagination['total_pages'];
|
||||
?>
|
||||
|
||||
<?php // Always show first 2 pages ?>
|
||||
<?php for ($i = 1; $i <= min(2, $total); $i++): ?>
|
||||
<button class="igny8-btn igny8-btn-sm igny8-page-btn <?php echo $i === $current ? 'igny8-btn-primary' : 'igny8-btn-outline'; ?>"
|
||||
data-page="<?php echo $i; ?>">
|
||||
<?php echo $i; ?>
|
||||
</button>
|
||||
<?php endfor; ?>
|
||||
|
||||
<?php // Add ellipsis if there's a gap ?>
|
||||
<?php if ($total > 4 && $current > 3): ?>
|
||||
<span style="margin: 0 8px; color: #666;">...</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php // Show current page if it's not in first 2 or last 2 ?>
|
||||
<?php if ($current > 2 && $current < $total - 1): ?>
|
||||
<button class="igny8-btn igny8-btn-sm igny8-page-btn igny8-btn-primary"
|
||||
data-page="<?php echo $current; ?>">
|
||||
<?php echo $current; ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php // Add ellipsis before last 2 if there's a gap ?>
|
||||
<?php if ($total > 4 && $current < $total - 2): ?>
|
||||
<span style="margin: 0 8px; color: #666;">...</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php // Always show last 2 pages (if different from first 2) ?>
|
||||
<?php for ($i = max(3, $total - 1); $i <= $total; $i++): ?>
|
||||
<?php if ($i > 2): // Don't duplicate first 2 pages ?>
|
||||
<button class="igny8-btn igny8-btn-sm igny8-page-btn <?php echo $i === $current ? 'igny8-btn-primary' : 'igny8-btn-outline'; ?>"
|
||||
data-page="<?php echo $i; ?>">
|
||||
<?php echo $i; ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php endfor; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<button class="igny8-btn igny8-btn-sm igny8-btn-outline igny8-page-btn"
|
||||
data-page="<?php echo min($pagination['total_pages'], $pagination['current_page'] + 1); ?>"
|
||||
<?php echo $pagination['current_page'] >= $pagination['total_pages'] ? 'disabled' : ''; ?>>
|
||||
Next ›
|
||||
</button>
|
||||
|
||||
<!-- Records per page selector -->
|
||||
<div class="igny8-per-page-selector" style="margin-left: 20px; display: inline-flex; align-items: center; gap: 8px;">
|
||||
<label for="<?php echo esc_attr($table_id); ?>_per_page" style="font-size: 12px; color: #666;">Show:</label>
|
||||
<select id="<?php echo esc_attr($table_id); ?>_per_page" class="igny8-per-page-select" data-table="<?php echo esc_attr($table_id); ?>" style="padding: 4px 8px; font-size: 12px; border: 1px solid #ddd; border-radius: 4px;">
|
||||
<option value="10" <?php selected($pagination['per_page'], 10); ?>>10</option>
|
||||
<option value="20" <?php selected($pagination['per_page'], 20); ?>>20</option>
|
||||
<option value="50" <?php selected($pagination['per_page'], 50); ?>>50</option>
|
||||
<option value="100" <?php selected($pagination['per_page'], 100); ?>>100</option>
|
||||
</select>
|
||||
<span style="font-size: 12px; color: #666;">per page</span>
|
||||
</div>
|
||||
|
||||
<span style="margin-left: 12px; font-size: 12px; color: #666;">
|
||||
Showing <?php echo (($pagination['current_page'] - 1) * $pagination['per_page']) + 1; ?>-<?php echo min($pagination['current_page'] * $pagination['per_page'], $pagination['total_items']); ?> of <?php echo $pagination['total_items']; ?> items
|
||||
</span>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
// Set default values
|
||||
$table_id = $table_id ?? 'data_table';
|
||||
$pagination = $pagination ?? [
|
||||
'current_page' => 1,
|
||||
'total_pages' => 1,
|
||||
'per_page' => 10,
|
||||
'total_items' => 0
|
||||
];
|
||||
$module = $module ?? '';
|
||||
$tab = $tab ?? '';
|
||||
|
||||
// Debug state: Pagination HTML rendered
|
||||
if (function_exists('igny8_debug_state')) {
|
||||
igny8_debug_state('PAGINATION_HTML_RENDERED', true, 'Pagination HTML rendered for ' . $table_id);
|
||||
}
|
||||
?>
|
||||
870
igny8-ai-seo-wp-plugin/modules/components/table-tpl.php
Normal file
870
igny8-ai-seo-wp-plugin/modules/components/table-tpl.php
Normal file
@@ -0,0 +1,870 @@
|
||||
<?php
|
||||
/**
|
||||
* ==========================
|
||||
* 🔐 IGNY8 FILE RULE HEADER
|
||||
* ==========================
|
||||
* @file : table-tpl.php
|
||||
* @location : /modules/components/table-tpl.php
|
||||
* @type : Component
|
||||
* @scope : Cross-Module
|
||||
* @allowed : Table rendering, data formatting, display functions
|
||||
* @reusability : Shared
|
||||
* @notes : Dynamic table component with formatting functions for all modules
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a column should have proper case transformation applied
|
||||
*
|
||||
* @param string $column_name The column name
|
||||
* @param array $column_config The column configuration
|
||||
* @return bool Whether to apply proper case transformation
|
||||
*/
|
||||
function igny8_should_apply_proper_case($column_name, $column_config) {
|
||||
// Only apply to enum fields
|
||||
$column_type = $column_config['type'] ?? 'text';
|
||||
return $column_type === 'enum';
|
||||
}
|
||||
|
||||
/**
|
||||
* Format enum field values with proper case display
|
||||
*
|
||||
* @param string $value The database value
|
||||
* @param string $field_name The field name
|
||||
* @return string The formatted display value
|
||||
*/
|
||||
function igny8_format_enum_field($value, $field_name) {
|
||||
if (empty($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// Handle specific field mappings for consistent display
|
||||
$field_mappings = [
|
||||
'status' => [
|
||||
'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 '<span class="text-muted">No prompts</span>';
|
||||
}
|
||||
|
||||
// Try to decode JSON
|
||||
$prompts = json_decode($image_prompts, true);
|
||||
if (!$prompts || !is_array($prompts)) {
|
||||
return '<span class="text-muted">Invalid format</span>';
|
||||
}
|
||||
|
||||
$output = '<div class="image-prompts-display">';
|
||||
$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 .= '<div class="prompt-item mb-1">';
|
||||
$output .= '<strong>' . esc_html($label) . ':</strong> ';
|
||||
$output .= '<span title="' . esc_attr($prompt) . '">' . esc_html($truncated) . '</span>';
|
||||
$output .= '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
if ($count === 0) {
|
||||
$output = '<span class="text-muted">No prompts</span>';
|
||||
} else {
|
||||
$output .= '</div>';
|
||||
}
|
||||
|
||||
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 = "<div class='igny8-structured-description'>";
|
||||
$formatted .= "<h4>Content Outline</h4>";
|
||||
|
||||
// Handle introduction section with hook
|
||||
if (!empty($structured_description['introduction'])) {
|
||||
$formatted .= "<div class='description-intro'>";
|
||||
|
||||
// Add hook if it exists
|
||||
if (!empty($structured_description['introduction']['hook'])) {
|
||||
$formatted .= "<div class='intro-hook'><strong>Hook:</strong> " . esc_html($structured_description['introduction']['hook']) . "</div>";
|
||||
}
|
||||
|
||||
// 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 .= "<div class='intro-paragraph'><strong>Intro Paragraph " . $paragraph_count . ":</strong> " . esc_html($paragraph['details']) . "</div>";
|
||||
$paragraph_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$formatted .= "</div>";
|
||||
}
|
||||
|
||||
// Handle H2 sections if they exist
|
||||
if (!empty($structured_description['H2']) && is_array($structured_description['H2'])) {
|
||||
foreach ($structured_description['H2'] as $h2_section) {
|
||||
$formatted .= "<div class='igny8-h2-section'>";
|
||||
$formatted .= "<h5>" . esc_html($h2_section['heading']) . "</h5>";
|
||||
|
||||
if (!empty($h2_section['subsections'])) {
|
||||
$formatted .= "<ul class='igny8-subsections'>";
|
||||
foreach ($h2_section['subsections'] as $h3_section) {
|
||||
$formatted .= "<li>";
|
||||
$formatted .= "<strong>" . esc_html($h3_section['subheading']) . "</strong>";
|
||||
$formatted .= " <span class='igny8-content-type'>(" . esc_html($h3_section['content_type']) . ")</span>";
|
||||
$formatted .= "<br><small>" . esc_html($h3_section['details']) . "</small>";
|
||||
$formatted .= "</li>";
|
||||
}
|
||||
$formatted .= "</ul>";
|
||||
}
|
||||
$formatted .= "</div>";
|
||||
}
|
||||
}
|
||||
|
||||
$formatted .= "</div>";
|
||||
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();
|
||||
?>
|
||||
<tr data-id="<?php echo esc_attr($id); ?>">
|
||||
<td><input type="checkbox" class="igny8-checkbox" value="<?php echo esc_attr($id); ?>"></td>
|
||||
<?php
|
||||
// If no config found, fallback to all available columns
|
||||
if (empty($column_keys)) {
|
||||
$column_keys = array_keys($row);
|
||||
}
|
||||
|
||||
foreach ($column_keys as $col) {
|
||||
// Check if this is a calculated field
|
||||
$column_config = $config_columns[$col] ?? [];
|
||||
if (isset($column_config['calculated']) && $column_config['calculated'] === true) {
|
||||
// Execute calculation query for this row
|
||||
$calculation_query = $column_config['calculation_query'] ?? '';
|
||||
if (!empty($calculation_query)) {
|
||||
// Replace placeholders in the query
|
||||
$query = str_replace(['{prefix}', '{table_name}'], [$wpdb->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;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<td>
|
||||
<div class="igny8-title-with-badge">
|
||||
<span class="igny8-title-text"><?php echo esc_html($value); ?></span>
|
||||
<div class="igny8-title-actions">
|
||||
<?php if (!empty($display_description)): ?>
|
||||
<button class="igny8-menu-toggle igny8-description-toggle"
|
||||
data-row-id="<?php echo esc_attr($id); ?>"
|
||||
data-description="<?php echo esc_attr($display_description); ?>"
|
||||
title="View description">
|
||||
<span class="igny8-hamburger">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</span>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($image_prompts)): ?>
|
||||
<button class="igny8-menu-toggle igny8-image-prompts-toggle"
|
||||
data-row-id="<?php echo esc_attr($id); ?>"
|
||||
data-image-prompts="<?php echo esc_attr($image_prompts); ?>"
|
||||
title="View image prompts">
|
||||
<span class="igny8-image-icon dashicons dashicons-format-image"></span>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<?php
|
||||
}
|
||||
// Special handling for title column in writer_tasks table
|
||||
elseif ($col === 'title' && $tableId === 'writer_tasks') {
|
||||
$description = isset($row['description']) ? $row['description'] : '';
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<td>
|
||||
<div class="igny8-title-with-badge">
|
||||
<span class="igny8-title-text"><?php echo esc_html($value); ?></span>
|
||||
<div class="igny8-title-actions">
|
||||
<?php if (!empty($display_description)): ?>
|
||||
<button class="igny8-menu-toggle igny8-description-toggle"
|
||||
data-row-id="<?php echo esc_attr($id); ?>"
|
||||
data-description="<?php echo esc_attr($display_description); ?>"
|
||||
title="View description">
|
||||
<span class="igny8-hamburger">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</span>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<?php
|
||||
}
|
||||
// Special handling for status column in planner_ideas table
|
||||
elseif ($col === 'status' && $tableId === 'planner_ideas') {
|
||||
$source = isset($row['source']) ? $row['source'] : '';
|
||||
?>
|
||||
<td>
|
||||
<div class="igny8-status-with-badge">
|
||||
<span class="igny8-status-text"><?php echo esc_html($value); ?></span>
|
||||
<?php if ($source === 'Manual'): ?>
|
||||
<span class="igny8-badge igny8-badge-dark-red">Manual</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
<td><?php echo esc_html($value); ?></td>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
<td class="igny8-align-center igny8-actions">
|
||||
<button
|
||||
class="igny8-icon-only igny8-icon-edit"
|
||||
data-action="editRow"
|
||||
data-row-id="<?php echo esc_attr($id); ?>"
|
||||
data-table-id="<?php echo esc_attr($tableId); ?>"
|
||||
title="Edit"
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
|
||||
<path d="m18.5 2.5 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="igny8-icon-only igny8-icon-delete" data-action="deleteRow" data-row-id="<?php echo esc_attr($id); ?>" title="Delete">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="3,6 5,6 21,6"></polyline>
|
||||
<path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
|
||||
<line x1="10" y1="11" x2="10" y2="17"></line>
|
||||
<line x1="14" y1="11" x2="14" y2="17"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
$table_body_html .= ob_get_clean();
|
||||
}
|
||||
} else {
|
||||
// No records found - add empty state
|
||||
ob_start();
|
||||
?>
|
||||
<tr>
|
||||
<td colspan="<?php echo count($column_keys) + 2; ?>" class="igny8-align-center igny8-text-muted">
|
||||
No records found
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
$table_body_html = ob_get_clean();
|
||||
}
|
||||
|
||||
$pagination_data = [
|
||||
'current_page' => $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();
|
||||
?>
|
||||
<!-- Data Table -->
|
||||
<div class="igny8-table" data-table="<?php echo esc_attr($table_id); ?>">
|
||||
|
||||
<table id="<?php echo esc_attr($table_id); ?>" class="igny8-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<input type="checkbox" id="<?php echo esc_attr($table_id); ?>_select_all" class="igny8-checkbox">
|
||||
</th>
|
||||
<?php
|
||||
// Render column headers dynamically based on table configuration
|
||||
if (!empty($columns)) {
|
||||
foreach ($columns as $column) {
|
||||
$key = $column['key'];
|
||||
$label = $column['label'];
|
||||
$sortable = $column['sortable'] ?? false;
|
||||
$sort_class = $sortable ? 'sortable' : '';
|
||||
$sort_indicator = $sortable ? ' <span class="sort-indicator">↕</span>' : '';
|
||||
?>
|
||||
<th class="<?php echo esc_attr($sort_class); ?>" data-column="<?php echo esc_attr($key); ?>">
|
||||
<?php echo esc_html($label); ?><?php echo $sort_indicator; ?>
|
||||
</th>
|
||||
<?php
|
||||
}
|
||||
} else {
|
||||
// Fallback headers if no config
|
||||
?>
|
||||
<th class="sortable" data-column="keyword">Keyword <span class="sort-indicator">↕</span></th>
|
||||
<th class="sortable" data-column="volume">Volume <span class="sort-indicator">↕</span></th>
|
||||
<th class="sortable" data-column="kd">KD <span class="sort-indicator">↕</span></th>
|
||||
<th class="sortable" data-column="cpc">CPC <span class="sort-indicator">↕</span></th>
|
||||
<th class="sortable" data-column="intent">Intent <span class="sort-indicator">↕</span></th>
|
||||
<th class="sortable" data-column="status">Status <span class="sort-indicator">↕</span></th>
|
||||
<th class="sortable" data-column="cluster">Cluster <span class="sort-indicator">↕</span></th>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="table-<?php echo esc_attr($table_id); ?>-body">
|
||||
<!-- Table data will be loaded dynamically via JavaScript -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
// Set default values
|
||||
$table_id = $table_id ?? 'data_table';
|
||||
$columns = $columns ?? [
|
||||
['key' => '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);
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user