486 lines
16 KiB
PHP
486 lines
16 KiB
PHP
<?php
|
|
/**
|
|
* ==========================
|
|
* 🔐 IGNY8 FILE RULE HEADER
|
|
* ==========================
|
|
* @file : sync-ajax.php
|
|
* @location : /flows/sync-ajax.php
|
|
* @type : AJAX Handler
|
|
* @scope : Global
|
|
* @allowed : AJAX endpoints, automation handlers, workflow operations
|
|
* @reusability : Globally Reusable
|
|
* @notes : Automation-specific AJAX handlers for all workflows
|
|
*/
|
|
|
|
// Prevent direct access
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Hook handlers for cluster metrics updates
|
|
*/
|
|
|
|
// Hook for when keywords are added
|
|
// Hook definitions moved to sync-hooks.php
|
|
|
|
|
|
/**
|
|
* Handle keyword cluster updates
|
|
*/
|
|
function igny8_handle_keyword_cluster_update($record_id, $cluster_id) {
|
|
if ($cluster_id) {
|
|
try {
|
|
igny8_update_cluster_metrics($cluster_id);
|
|
} catch (Exception $e) {
|
|
error_log("ERROR in igny8_handle_keyword_cluster_update: " . $e->getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* AJAX handler for keyword imports with workflow automation
|
|
*/
|
|
// Hook moved to sync-hooks.php
|
|
function igny8_ajax_import_keywords() {
|
|
// Verify nonce
|
|
if (!wp_verify_nonce($_POST['nonce'], 'igny8_ajax_nonce')) {
|
|
wp_send_json_error('Security check failed');
|
|
}
|
|
|
|
// Check user capabilities
|
|
if (!current_user_can('manage_options')) {
|
|
wp_send_json_error('Insufficient permissions');
|
|
}
|
|
|
|
$keywords_data = $_POST['keywords'] ?? [];
|
|
|
|
if (empty($keywords_data) || !is_array($keywords_data)) {
|
|
wp_send_json_error('No keywords data provided');
|
|
}
|
|
|
|
global $wpdb;
|
|
$imported_ids = [];
|
|
|
|
try {
|
|
// Import keywords
|
|
foreach ($keywords_data as $keyword_data) {
|
|
$sanitized_data = [
|
|
'keyword' => sanitize_text_field($keyword_data['keyword'] ?? ''),
|
|
'search_volume' => intval($keyword_data['search_volume'] ?? 0),
|
|
'difficulty' => sanitize_text_field($keyword_data['difficulty'] ?? ''),
|
|
'cpc' => floatval($keyword_data['cpc'] ?? 0),
|
|
'intent' => sanitize_text_field($keyword_data['intent'] ?? ''),
|
|
'status' => 'unmapped',
|
|
'created_at' => current_time('mysql'),
|
|
'updated_at' => current_time('mysql')
|
|
];
|
|
|
|
// Skip empty keywords
|
|
if (empty($sanitized_data['keyword'])) {
|
|
continue;
|
|
}
|
|
|
|
$result = $wpdb->insert(
|
|
$wpdb->prefix . 'igny8_keywords',
|
|
$sanitized_data,
|
|
['%s', '%d', '%s', '%f', '%s', '%s', '%s', '%s']
|
|
);
|
|
|
|
if ($result !== false) {
|
|
$imported_ids[] = $wpdb->insert_id;
|
|
}
|
|
}
|
|
|
|
if (empty($imported_ids)) {
|
|
wp_send_json_error('No keywords were imported');
|
|
}
|
|
|
|
// Trigger workflow automation for imported keywords
|
|
$workflow_result = igny8_workflow_triggers('keywords_imported', ['keyword_ids' => $imported_ids]);
|
|
|
|
// Prepare response
|
|
$response = [
|
|
'message' => 'Keywords imported successfully',
|
|
'imported_count' => count($imported_ids),
|
|
'keyword_ids' => $imported_ids
|
|
];
|
|
|
|
if ($workflow_result && $workflow_result['success']) {
|
|
$response['workflow_message'] = $workflow_result['message'];
|
|
if (isset($workflow_result['clusters_created'])) {
|
|
$response['workflow_data'] = [
|
|
'clusters_created' => $workflow_result['clusters_created'],
|
|
'cluster_ids' => $workflow_result['cluster_ids'] ?? []
|
|
];
|
|
}
|
|
}
|
|
|
|
wp_send_json_success($response);
|
|
|
|
} catch (Exception $e) {
|
|
wp_send_json_error('Import error: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for creating a single task from an idea
|
|
*/
|
|
// Hook moved to sync-hooks.php
|
|
function igny8_create_task_from_idea_ajax() {
|
|
try {
|
|
check_ajax_referer('igny8_ajax_nonce', 'nonce');
|
|
if (!current_user_can('edit_posts')) {
|
|
wp_send_json_error(['message' => 'Unauthorized']);
|
|
}
|
|
|
|
$idea_id = isset($_POST['idea_id']) ? absint($_POST['idea_id']) : 0;
|
|
if (!$idea_id) {
|
|
wp_send_json_error(['message' => 'Invalid idea id']);
|
|
}
|
|
|
|
$result = igny8_create_task_from_idea($idea_id);
|
|
wp_send_json($result);
|
|
} catch (Exception $e) {
|
|
wp_send_json_error(['message' => 'Error: ' . $e->getMessage()]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for bulk creating tasks from ideas
|
|
*/
|
|
// Hook moved to sync-hooks.php
|
|
function igny8_bulk_create_tasks_from_ideas_ajax() {
|
|
try {
|
|
check_ajax_referer('igny8_ajax_nonce', 'nonce');
|
|
if (!current_user_can('edit_posts')) {
|
|
wp_send_json_error(['message' => 'Unauthorized']);
|
|
}
|
|
|
|
$ids = isset($_POST['idea_ids']) ? array_map('absint', (array)$_POST['idea_ids']) : [];
|
|
$ids = array_values(array_filter($ids));
|
|
if (empty($ids)) {
|
|
wp_send_json_error(['message' => 'No idea ids provided']);
|
|
}
|
|
|
|
// Check if ideas have status other than 'new'
|
|
global $wpdb;
|
|
$placeholders = implode(',', array_fill(0, count($ids), '%d'));
|
|
$ideas_status = $wpdb->get_results($wpdb->prepare("
|
|
SELECT id, idea_title, status FROM {$wpdb->prefix}igny8_content_ideas
|
|
WHERE id IN ({$placeholders}) AND status != 'new'
|
|
", $ids));
|
|
|
|
if (!empty($ideas_status)) {
|
|
$idea_titles = array_column($ideas_status, 'idea_title');
|
|
wp_send_json_error(['message' => 'Ideas are not in "new" status: ' . implode(', ', array_slice($idea_titles, 0, 3)) . (count($idea_titles) > 3 ? '...' : '')]);
|
|
}
|
|
|
|
$created = [];
|
|
$skipped = [];
|
|
$failed = [];
|
|
|
|
foreach ($ids as $id) {
|
|
$res = igny8_create_task_from_idea($id);
|
|
if (!empty($res['success'])) {
|
|
if (!empty($res['task_id'])) {
|
|
$created[] = $res['task_id'];
|
|
} else {
|
|
$skipped[] = $id;
|
|
}
|
|
} else {
|
|
$failed[] = $id;
|
|
}
|
|
}
|
|
|
|
// Update metrics once per unique idea id to be safe
|
|
foreach ($ids as $id) {
|
|
igny8_update_idea_metrics($id);
|
|
}
|
|
|
|
wp_send_json_success([
|
|
'created' => $created,
|
|
'skipped' => $skipped,
|
|
'failed' => $failed,
|
|
'message' => sprintf('Created %d, skipped %d, failed %d', count($created), count($skipped), count($failed))
|
|
]);
|
|
} catch (Exception $e) {
|
|
wp_send_json_error(['message' => 'Error: ' . $e->getMessage()]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function to create a task from an idea
|
|
*
|
|
* @param int $idea_id The idea ID to create task from
|
|
* @return array Result array with success status and message
|
|
*/
|
|
function igny8_create_task_from_idea($idea_id) {
|
|
global $wpdb;
|
|
|
|
$idea = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT id, idea_title, idea_description, content_structure, content_type, keyword_cluster_id, estimated_word_count, target_keywords
|
|
FROM {$wpdb->prefix}igny8_content_ideas WHERE id=%d",
|
|
$idea_id
|
|
));
|
|
|
|
if (!$idea) {
|
|
return ['success' => false, 'message' => 'Idea not found'];
|
|
}
|
|
|
|
// Optional dedupe policy: One open task per idea
|
|
$existing = $wpdb->get_var($wpdb->prepare(
|
|
"SELECT id FROM {$wpdb->prefix}igny8_tasks WHERE idea_id=%d AND status IN ('draft','queued','in_progress','review') LIMIT 1",
|
|
$idea_id
|
|
));
|
|
|
|
if ($existing) {
|
|
return ['success' => true, 'message' => 'Task already exists for this idea', 'task_id' => (int)$existing];
|
|
}
|
|
|
|
$ins = $wpdb->insert($wpdb->prefix . 'igny8_tasks', [
|
|
'title' => $idea->idea_title,
|
|
'description' => $idea->idea_description,
|
|
'content_structure' => $idea->content_structure ?: 'cluster_hub',
|
|
'content_type' => $idea->content_type ?: 'post',
|
|
'cluster_id' => $idea->keyword_cluster_id ?: null,
|
|
'priority' => 'medium',
|
|
'status' => 'queued',
|
|
'idea_id' => (int)$idea_id,
|
|
'keywords' => $idea->target_keywords ?: '',
|
|
'word_count' => $idea->estimated_word_count ?: 0,
|
|
'schedule_at' => null,
|
|
'assigned_post_id' => null,
|
|
'created_at' => current_time('mysql'),
|
|
'updated_at' => current_time('mysql')
|
|
], ['%s', '%s', '%s', '%s', '%d', '%s', '%s', '%d', '%s', '%d', '%s', '%d', '%s', '%s']);
|
|
|
|
if ($ins === false) {
|
|
return ['success' => false, 'message' => 'Failed to create task'];
|
|
}
|
|
|
|
$task_id = (int)$wpdb->insert_id;
|
|
|
|
// Update idea status to 'scheduled' when successfully queued to writer
|
|
$wpdb->update(
|
|
$wpdb->prefix . 'igny8_content_ideas',
|
|
['status' => 'scheduled'],
|
|
['id' => $idea_id],
|
|
['%s'],
|
|
['%d']
|
|
);
|
|
|
|
// Update keyword status to 'queued' when task is created from idea
|
|
if ($idea->keyword_cluster_id) {
|
|
// Get all keywords in this cluster and update their status to 'queued'
|
|
$keyword_ids = $wpdb->get_col($wpdb->prepare("
|
|
SELECT id FROM {$wpdb->prefix}igny8_keywords
|
|
WHERE cluster_id = %d
|
|
", $idea->keyword_cluster_id));
|
|
|
|
if (!empty($keyword_ids)) {
|
|
$placeholders = implode(',', array_fill(0, count($keyword_ids), '%d'));
|
|
$wpdb->query($wpdb->prepare("
|
|
UPDATE {$wpdb->prefix}igny8_keywords
|
|
SET status = 'queued'
|
|
WHERE id IN ({$placeholders})
|
|
", $keyword_ids));
|
|
}
|
|
}
|
|
|
|
igny8_update_idea_metrics($idea_id);
|
|
igny8_write_log('queue_to_writer', ['idea_id' => $idea_id, 'task_id' => $task_id]);
|
|
|
|
return ['success' => true, 'message' => "Task queued for Writer", "task_id" => $task_id];
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for bulk deleting keywords
|
|
*/
|
|
// Hook moved to sync-hooks.php
|
|
function igny8_ajax_bulk_delete_keywords() {
|
|
// Verify nonce for security
|
|
if (!check_ajax_referer('igny8_ajax_nonce', 'nonce', true)) {
|
|
wp_send_json_error(['message' => 'Security check failed.']);
|
|
}
|
|
|
|
// Check user capabilities
|
|
if (!current_user_can('manage_options')) {
|
|
wp_send_json_error(['message' => 'You do not have permission to perform this action.']);
|
|
}
|
|
|
|
// Get parameters
|
|
$keyword_ids = $_POST['keyword_ids'] ?? [];
|
|
|
|
if (empty($keyword_ids) || !is_array($keyword_ids)) {
|
|
wp_send_json_error(['message' => 'No keywords selected for deletion.']);
|
|
}
|
|
|
|
// Call bulk delete function
|
|
$result = igny8_bulk_delete_keywords($keyword_ids);
|
|
|
|
if ($result['success']) {
|
|
wp_send_json_success([
|
|
'message' => $result['message'],
|
|
'deleted_count' => $result['deleted_count']
|
|
]);
|
|
} else {
|
|
wp_send_json_error(['message' => $result['message']]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for bulk mapping keywords to cluster
|
|
*/
|
|
// Hook moved to sync-hooks.php
|
|
function igny8_ajax_bulk_map_keywords() {
|
|
// Verify nonce for security
|
|
if (!check_ajax_referer('igny8_ajax_nonce', 'nonce', true)) {
|
|
wp_send_json_error(['message' => 'Security check failed.']);
|
|
}
|
|
|
|
// Check user capabilities
|
|
if (!current_user_can('manage_options')) {
|
|
wp_send_json_error(['message' => 'You do not have permission to perform this action.']);
|
|
}
|
|
|
|
// Get parameters
|
|
$keyword_ids = $_POST['keyword_ids'] ?? [];
|
|
$cluster_id = intval($_POST['cluster_id'] ?? 0);
|
|
|
|
if (empty($keyword_ids) || !is_array($keyword_ids)) {
|
|
wp_send_json_error(['message' => 'No keywords selected for mapping.']);
|
|
}
|
|
|
|
if (empty($cluster_id)) {
|
|
wp_send_json_error(['message' => 'No cluster selected for mapping.']);
|
|
}
|
|
|
|
// Call bulk map function
|
|
$result = igny8_bulk_map_keywords($keyword_ids, $cluster_id);
|
|
|
|
if ($result['success']) {
|
|
wp_send_json_success([
|
|
'message' => $result['message'],
|
|
'mapped_count' => $result['mapped_count']
|
|
]);
|
|
} else {
|
|
wp_send_json_error(['message' => $result['message']]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for bulk unmapping keywords from clusters
|
|
*/
|
|
// Hook moved to sync-hooks.php
|
|
function igny8_ajax_bulk_unmap_keywords() {
|
|
// Verify nonce for security
|
|
if (!check_ajax_referer('igny8_ajax_nonce', 'nonce', true)) {
|
|
wp_send_json_error(['message' => 'Security check failed.']);
|
|
}
|
|
|
|
// Check user capabilities
|
|
if (!current_user_can('manage_options')) {
|
|
wp_send_json_error(['message' => 'You do not have permission to perform this action.']);
|
|
}
|
|
|
|
// Get parameters
|
|
$keyword_ids = $_POST['keyword_ids'] ?? [];
|
|
|
|
if (empty($keyword_ids) || !is_array($keyword_ids)) {
|
|
wp_send_json_error(['message' => 'No keywords selected for unmapping.']);
|
|
}
|
|
|
|
// Call bulk unmap function
|
|
$result = igny8_bulk_unmap_keywords($keyword_ids);
|
|
|
|
if ($result['success']) {
|
|
wp_send_json_success([
|
|
'message' => $result['message'],
|
|
'unmapped_count' => $result['unmapped_count']
|
|
]);
|
|
} else {
|
|
wp_send_json_error(['message' => $result['message']]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for bulk deleting records
|
|
*/
|
|
// Hook moved to sync-hooks.php
|
|
function igny8_delete_bulk_records() {
|
|
// Verify nonce
|
|
if (!wp_verify_nonce($_POST['nonce'], 'igny8_ajax_nonce')) {
|
|
wp_send_json_error('Security check failed');
|
|
}
|
|
|
|
// Check user capabilities
|
|
if (!current_user_can('manage_options')) {
|
|
wp_send_json_error('Insufficient permissions');
|
|
}
|
|
|
|
// Get parameters
|
|
$table_id = sanitize_text_field($_POST['table_id'] ?? '');
|
|
$record_ids = $_POST['record_ids'] ?? [];
|
|
|
|
if (empty($table_id) || empty($record_ids) || !is_array($record_ids)) {
|
|
wp_send_json_error('Table ID and Record IDs required');
|
|
}
|
|
|
|
// Get table name
|
|
$table_name = igny8_get_table_name($table_id);
|
|
if (!$table_name) {
|
|
wp_send_json_error('Invalid table ID');
|
|
}
|
|
|
|
// Sanitize IDs
|
|
$record_ids = array_map('intval', $record_ids);
|
|
$record_ids = array_filter($record_ids, function($id) { return $id > 0; });
|
|
|
|
if (empty($record_ids)) {
|
|
wp_send_json_error('No valid record IDs provided');
|
|
}
|
|
|
|
global $wpdb;
|
|
|
|
// Handle cluster deletion - clean up keyword relationships
|
|
if ($table_id === 'planner_clusters') {
|
|
// Before deleting clusters, unmap all keywords from these clusters
|
|
$placeholders = implode(',', array_fill(0, count($record_ids), '%d'));
|
|
$unmapped_count = $wpdb->query($wpdb->prepare(
|
|
"UPDATE {$wpdb->prefix}igny8_keywords
|
|
SET cluster_id = NULL, status = 'unmapped', updated_at = CURRENT_TIMESTAMP
|
|
WHERE cluster_id IN ({$placeholders})",
|
|
$record_ids
|
|
));
|
|
|
|
if ($unmapped_count !== false) {
|
|
// Log the unmapping
|
|
error_log("Igny8: Unmapped {$unmapped_count} keywords from deleted clusters: " . implode(',', $record_ids));
|
|
}
|
|
}
|
|
|
|
// Build placeholders for IN clause
|
|
$placeholders = implode(',', array_fill(0, count($record_ids), '%d'));
|
|
|
|
try {
|
|
$result = $wpdb->query($wpdb->prepare(
|
|
"DELETE FROM `{$table_name}` WHERE id IN ({$placeholders})",
|
|
$record_ids
|
|
));
|
|
|
|
if ($result === false) {
|
|
wp_send_json_error('Failed to delete records');
|
|
}
|
|
|
|
wp_send_json_success([
|
|
'message' => "Successfully deleted {$result} record(s)",
|
|
'deleted_count' => $result
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
wp_send_json_error('Database error: ' . $e->getMessage());
|
|
}
|
|
}
|