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()); } }