Files
igny8/igny8-ai-seo-wp-plugin/flows/sync-functions.php
2025-11-11 21:16:37 +05:00

673 lines
21 KiB
PHP

<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : sync-functions.php
* @location : /flows/sync-functions.php
* @type : Function Library
* @scope : Global
* @allowed : Automation logic, workflow functions, sync operations
* @reusability : Globally Reusable
* @notes : Core automation functions for all workflows
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Update cluster metrics based on related keywords and mappings
*/
function igny8_update_cluster_metrics($cluster_id) {
global $wpdb;
if (!$cluster_id) {
return ['success' => false, 'message' => 'Operation failed'];
}
try {
// Get keyword count and aggregated data
$keyword_data = $wpdb->get_row($wpdb->prepare("
SELECT
COUNT(*) as keyword_count,
COALESCE(SUM(search_volume), 0) as total_volume,
COALESCE(AVG(difficulty), 0) as avg_difficulty
FROM {$wpdb->prefix}igny8_keywords
WHERE cluster_id = %d
", $cluster_id));
// Get mapped pages count from taxonomy relationships
$cluster_term_id = $wpdb->get_var($wpdb->prepare("
SELECT cluster_term_id FROM {$wpdb->prefix}igny8_clusters WHERE id = %d
", $cluster_id));
$mapped_pages_count = 0;
if ($cluster_term_id) {
// Count content (posts, pages, products) associated with this cluster term
$mapped_pages_count = $wpdb->get_var($wpdb->prepare("
SELECT COUNT(DISTINCT object_id)
FROM {$wpdb->prefix}term_relationships tr
INNER JOIN {$wpdb->prefix}term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
WHERE tt.term_id = %d AND tt.taxonomy = 'clusters'
", $cluster_term_id));
}
// Update cluster record
$wpdb->update(
$wpdb->prefix . 'igny8_clusters',
[
'keyword_count' => intval($keyword_data->keyword_count),
'total_volume' => intval($keyword_data->total_volume),
'avg_difficulty' => floatval($keyword_data->avg_difficulty),
'mapped_pages_count' => intval($mapped_pages_count)
],
['id' => $cluster_id],
['%d', '%d', '%f', '%d'],
['%d']
);
return true;
} catch (Exception $e) {
error_log("ERROR in igny8_update_cluster_metrics: " . $e->getMessage());
return ['success' => false, 'message' => 'Failed to update cluster metrics: ' . $e->getMessage()];
}
}
/**
* Update campaign metrics
*/
function igny8_update_campaign_metrics($campaign_id) {
global $wpdb;
if (!$campaign_id) {
return ['success' => false, 'message' => 'Operation failed'];
}
// Get campaign performance data
$performance_data = $wpdb->get_row($wpdb->prepare("
SELECT
COUNT(*) as total_tasks,
COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed_tasks,
COALESCE(AVG(seo_score), 0) as avg_seo_score
FROM {$wpdb->prefix}igny8_tasks
WHERE campaign_id = %d
", $campaign_id));
// Update campaign record
$wpdb->update(
$wpdb->prefix . 'igny8_campaigns',
[
'total_tasks' => intval($performance_data->total_tasks),
'completed_tasks' => intval($performance_data->completed_tasks),
'avg_seo_score' => floatval($performance_data->avg_seo_score)
],
['id' => $campaign_id],
['%d', '%d', '%f'],
['%d']
);
return true;
}
/**
* Update idea metrics based on related tasks
*/
function igny8_update_idea_metrics($idea_id) {
global $wpdb;
if (!$idea_id) {
return ['success' => false, 'message' => 'Operation failed'];
}
// Get tasks count for this idea
$tasks_count = $wpdb->get_var($wpdb->prepare("
SELECT COUNT(*)
FROM {$wpdb->prefix}igny8_tasks
WHERE idea_id = %d
", $idea_id));
// Update idea record
$wpdb->update(
$wpdb->prefix . 'igny8_content_ideas',
[
'tasks_count' => intval($tasks_count)
],
['id' => $idea_id],
['%d'],
['%d']
);
return true;
}
/**
* Update task metrics based on related variations
*/
// REMOVED: Task variations functionality - tasks don't need variations
/**
* Update keyword status when WordPress post is published
*
* @param int $post_id The WordPress post ID that was published
*/
function igny8_update_keywords_on_post_publish($post_id) {
global $wpdb;
if (!$post_id) {
return;
}
// Find tasks that are assigned to this post
$tasks = $wpdb->get_results($wpdb->prepare("
SELECT id, cluster_id
FROM {$wpdb->prefix}igny8_tasks
WHERE assigned_post_id = %d AND cluster_id IS NOT NULL
", $post_id));
if (empty($tasks)) {
return;
}
// Update keyword status to 'mapped' for all keywords in the clusters of published tasks
foreach ($tasks as $task) {
if ($task->cluster_id) {
// Get all keywords in this cluster and update their status to 'mapped'
$keyword_ids = $wpdb->get_col($wpdb->prepare("
SELECT id FROM {$wpdb->prefix}igny8_keywords
WHERE cluster_id = %d
", $task->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 = 'mapped'
WHERE id IN ({$placeholders})
", $keyword_ids));
}
// Update task status to 'completed'
$wpdb->update(
$wpdb->prefix . 'igny8_tasks',
['status' => 'completed'],
['id' => $task->id],
['%s'],
['%d']
);
}
}
// Log the event
if (function_exists('igny8_write_log')) {
igny8_write_log('post_published', [
'post_id' => $post_id,
'tasks_updated' => count($tasks),
'message' => 'Keywords updated to mapped status'
]);
}
}
/**
* Automatically create clusters from keywords using AI clustering
*
* @param array $keyword_ids Optional array of keyword IDs to cluster. If empty, uses all unmapped keywords.
* @return array Result array with success status and message
*/
function igny8_auto_create_clusters_from_keywords($keyword_ids = []) {
global $wpdb;
// If no keyword IDs provided, get all unmapped keywords
if (empty($keyword_ids)) {
$keyword_ids = $wpdb->get_col("
SELECT id FROM {$wpdb->prefix}igny8_keywords
WHERE cluster_id IS NULL
");
}
if (empty($keyword_ids)) {
return ['success' => true, 'message' => 'No unmapped keywords found for clustering'];
}
// Limit to 20 keywords for AI processing
if (count($keyword_ids) > 20) {
$keyword_ids = array_slice($keyword_ids, 0, 20);
}
// Get keywords data
$placeholders = implode(',', array_fill(0, count($keyword_ids), '%d'));
$keywords = $wpdb->get_results($wpdb->prepare("
SELECT * FROM {$wpdb->prefix}igny8_keywords
WHERE id IN ({$placeholders})
", $keyword_ids));
if (empty($keywords)) {
return ['success' => false, 'message' => 'No valid keywords found for clustering'];
}
// Create clusters using AI (this would call the AI clustering function)
// For now, create a simple cluster with all keywords
$cluster_name = igny8_generate_cluster_name_from_keywords(array_column($keywords, 'keyword'));
// Create cluster
$cluster_result = $wpdb->insert(
$wpdb->prefix . 'igny8_clusters',
[
'cluster_name' => $cluster_name,
'status' => 'active',
'created_at' => current_time('mysql')
],
['%s', '%s', '%s']
);
if ($cluster_result === false) {
return ['success' => false, 'message' => 'Failed to create cluster'];
}
$cluster_id = $wpdb->insert_id;
// Trigger cluster_added action to create taxonomy term
do_action('igny8_cluster_added', $cluster_id);
// Map all keywords to this cluster
$mapped_count = $wpdb->query($wpdb->prepare("
UPDATE {$wpdb->prefix}igny8_keywords
SET cluster_id = %d, status = 'mapped'
WHERE id IN ({$placeholders})
", array_merge([$cluster_id], $keyword_ids)));
// Update cluster metrics
igny8_update_cluster_metrics($cluster_id);
return [
'success' => true,
'message' => "Created cluster '{$cluster_name}' with {$mapped_count} keywords",
'cluster_id' => $cluster_id,
'mapped_count' => $mapped_count
];
}
/**
* Workflow automation trigger system
*/
function igny8_workflow_triggers($event, $payload = []) {
switch ($event) {
case 'keywords_imported':
if (isset($payload['keyword_ids'])) {
return igny8_auto_create_clusters_from_keywords($payload['keyword_ids']);
}
break;
case 'cluster_created':
if (isset($payload['cluster_id'])) {
// Create ideas from cluster
$ideas_result = igny8_auto_create_ideas_from_clusters($payload['cluster_id']);
return [
'success' => true,
'message' => 'Cluster workflow completed',
'ideas' => $ideas_result
];
}
break;
}
return ['success' => true, 'message' => 'No workflow automation for this event'];
}
/**
* Bulk delete keywords with cluster metrics update
*/
function igny8_bulk_delete_keywords($keyword_ids) {
global $wpdb;
if (empty($keyword_ids) || !is_array($keyword_ids)) {
return ['success' => false, 'message' => 'Operation failed'];
}
// Get cluster IDs before deletion for metrics update
$placeholders = implode(',', array_fill(0, count($keyword_ids), '%d'));
$cluster_ids = $wpdb->get_col($wpdb->prepare("
SELECT DISTINCT cluster_id
FROM {$wpdb->prefix}igny8_keywords
WHERE id IN ({$placeholders})
AND cluster_id IS NOT NULL
", $keyword_ids));
// Delete keywords
$result = $wpdb->query($wpdb->prepare("
DELETE FROM {$wpdb->prefix}igny8_keywords
WHERE id IN ({$placeholders})
", $keyword_ids));
if ($result !== false) {
// Update cluster metrics for affected clusters
foreach ($cluster_ids as $cluster_id) {
if ($cluster_id) {
igny8_update_cluster_metrics($cluster_id);
}
}
return $result;
}
return ['success' => false, 'message' => 'Operation failed'];
}
/**
* Bulk map keywords to cluster
*
* @param array $keyword_ids Array of keyword IDs to map
* @param int $cluster_id Cluster ID to map keywords to
* @return array ['success' => bool, 'message' => string, 'mapped_count' => int]
*/
function igny8_bulk_map_keywords($keyword_ids, $cluster_id) {
global $wpdb;
if (empty($keyword_ids) || !is_array($keyword_ids)) {
return ['success' => false, 'message' => 'No keywords selected for mapping', 'mapped_count' => 0];
}
if (empty($cluster_id) || !is_numeric($cluster_id)) {
return ['success' => false, 'message' => 'Invalid cluster ID provided', 'mapped_count' => 0];
}
$cluster_id = intval($cluster_id);
// Verify cluster exists
$cluster_exists = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->prefix}igny8_clusters WHERE id = %d",
$cluster_id
));
if (!$cluster_exists) {
return ['success' => false, 'message' => 'Cluster not found', 'mapped_count' => 0];
}
// Sanitize and validate IDs
$keyword_ids = array_map('intval', $keyword_ids);
$keyword_ids = array_filter($keyword_ids, function($id) { return $id > 0; });
if (empty($keyword_ids)) {
return ['success' => false, 'message' => 'No valid keyword IDs provided', 'mapped_count' => 0];
}
// Get old cluster IDs for metrics update
$placeholders = implode(',', array_fill(0, count($keyword_ids), '%d'));
$old_cluster_ids = $wpdb->get_col($wpdb->prepare(
"SELECT DISTINCT cluster_id FROM {$wpdb->prefix}igny8_keywords WHERE id IN ({$placeholders}) AND cluster_id IS NOT NULL",
$keyword_ids
));
// Update keywords to new cluster
$mapped_count = $wpdb->query($wpdb->prepare(
"UPDATE {$wpdb->prefix}igny8_keywords SET cluster_id = %d, status = 'mapped', updated_at = CURRENT_TIMESTAMP WHERE id IN ({$placeholders})",
array_merge([$cluster_id], $keyword_ids)
));
if ($mapped_count === false) {
return ['success' => false, 'message' => 'Failed to map keywords to cluster', 'mapped_count' => 0];
}
// Update metrics for old clusters (they lost keywords)
if (!empty($old_cluster_ids)) {
foreach (array_unique($old_cluster_ids) as $old_cluster_id) {
if ($old_cluster_id && $old_cluster_id != $cluster_id) {
igny8_update_cluster_metrics($old_cluster_id);
}
}
}
// Update metrics for new cluster (gained keywords)
igny8_update_cluster_metrics($cluster_id);
return [
'success' => true,
'message' => "Successfully mapped {$mapped_count} keyword(s) to cluster",
'mapped_count' => $mapped_count
];
}
/**
* Bulk unmap keywords from their clusters
*
* @param array $keyword_ids Array of keyword IDs to unmap
* @return array ['success' => bool, 'message' => string, 'unmapped_count' => int]
*/
function igny8_bulk_unmap_keywords($keyword_ids) {
global $wpdb;
if (empty($keyword_ids) || !is_array($keyword_ids)) {
return ['success' => false, 'message' => 'No keywords selected for unmapping', 'unmapped_count' => 0];
}
// Sanitize and validate IDs
$keyword_ids = array_map('intval', $keyword_ids);
$keyword_ids = array_filter($keyword_ids, function($id) { return $id > 0; });
if (empty($keyword_ids)) {
return ['success' => false, 'message' => 'No valid keyword IDs provided', 'unmapped_count' => 0];
}
// Get cluster IDs before unmapping for metrics update
$placeholders = implode(',', array_fill(0, count($keyword_ids), '%d'));
$cluster_ids = $wpdb->get_col($wpdb->prepare(
"SELECT DISTINCT cluster_id FROM {$wpdb->prefix}igny8_keywords WHERE id IN ({$placeholders}) AND cluster_id IS NOT NULL",
$keyword_ids
));
// Update keywords to unmap them
$unmapped_count = $wpdb->query($wpdb->prepare(
"UPDATE {$wpdb->prefix}igny8_keywords SET cluster_id = NULL, status = 'unmapped', updated_at = CURRENT_TIMESTAMP WHERE id IN ({$placeholders})",
$keyword_ids
));
if ($unmapped_count === false) {
return ['success' => false, 'message' => 'Failed to unmap keywords from clusters', 'unmapped_count' => 0];
}
// Update metrics for affected clusters (they lost keywords)
if (!empty($cluster_ids)) {
foreach (array_unique($cluster_ids) as $cluster_id) {
if ($cluster_id) {
igny8_update_cluster_metrics($cluster_id);
}
}
}
return [
'success' => true,
'message' => "Successfully unmapped {$unmapped_count} keyword(s) from clusters",
'unmapped_count' => $unmapped_count
];
}
/**
* Automatically create or link a taxonomy term when a new cluster is created.
*
* @param int $cluster_id The cluster ID from wp_igny8_clusters.
*/
function igny8_auto_create_cluster_term($cluster_id) {
global $wpdb;
// 1. Validate cluster
$cluster = $wpdb->get_row($wpdb->prepare(
"SELECT cluster_name, cluster_term_id FROM {$wpdb->prefix}igny8_clusters WHERE id = %d",
$cluster_id
));
if (!$cluster) {
error_log("Igny8: Cluster not found for ID $cluster_id.");
return;
}
// 2. Skip if already mapped
if (!empty($cluster->cluster_term_id)) {
error_log("Igny8: Cluster $cluster_id already linked to term {$cluster->cluster_term_id}.");
return;
}
// 3. Ensure taxonomy exists
if (!taxonomy_exists('clusters') && function_exists('igny8_register_taxonomies')) {
igny8_register_taxonomies();
}
// 4. Create or find existing term
$term_result = wp_insert_term(
sanitize_text_field($cluster->cluster_name),
'clusters',
['slug' => sanitize_title($cluster->cluster_name)]
);
if (is_wp_error($term_result)) {
// If term exists, fetch it
$existing = get_term_by('name', $cluster->cluster_name, 'clusters');
$term_id = $existing ? $existing->term_id : 0;
} else {
$term_id = $term_result['term_id'];
}
// 5. Save term ID back to cluster table
if ($term_id > 0) {
$wpdb->update(
"{$wpdb->prefix}igny8_clusters",
['cluster_term_id' => $term_id],
['id' => $cluster_id],
['%d'],
['%d']
);
error_log("Igny8: Created and linked taxonomy term $term_id for cluster $cluster_id.");
} else {
error_log("Igny8: Failed to link taxonomy term for cluster $cluster_id.");
}
}
/**
* Automatically update taxonomy term when a cluster name is changed/updated.
*
* @param int $cluster_id The cluster ID from wp_igny8_clusters.
*/
function igny8_auto_update_cluster_term($cluster_id) {
global $wpdb;
// 1. Validate cluster
$cluster = $wpdb->get_row($wpdb->prepare(
"SELECT cluster_name, cluster_term_id FROM {$wpdb->prefix}igny8_clusters WHERE id = %d",
$cluster_id
));
if (!$cluster) {
error_log("Igny8: Cluster not found for ID $cluster_id.");
return;
}
// 2. Skip if no term linked
if (empty($cluster->cluster_term_id)) {
error_log("Igny8: Cluster $cluster_id has no linked taxonomy term.");
return;
}
// 3. Ensure taxonomy exists
if (!taxonomy_exists('clusters') && function_exists('igny8_register_taxonomies')) {
igny8_register_taxonomies();
}
// 4. Get the existing term
$term = get_term($cluster->cluster_term_id, 'clusters');
if (is_wp_error($term) || !$term) {
error_log("Igny8: Taxonomy term {$cluster->cluster_term_id} not found for cluster $cluster_id.");
return;
}
// 5. Check if name has changed
if ($term->name === $cluster->cluster_name) {
error_log("Igny8: Cluster $cluster_id name unchanged, skipping taxonomy update.");
return;
}
// 6. Update the term name and slug
$update_result = wp_update_term(
$cluster->cluster_term_id,
'clusters',
[
'name' => sanitize_text_field($cluster->cluster_name),
'slug' => sanitize_title($cluster->cluster_name)
]
);
if (is_wp_error($update_result)) {
error_log("Igny8: Failed to update taxonomy term for cluster $cluster_id: " . $update_result->get_error_message());
} else {
error_log("Igny8: Successfully updated taxonomy term {$cluster->cluster_term_id} for cluster $cluster_id.");
}
}
// Hook registration for automatic cluster term creation
// Hook moved to sync-hooks.php
/**
* Handle content association/disassociation with cluster taxonomy terms
*/
function igny8_handle_content_cluster_association($object_id, $terms, $tt_ids, $taxonomy) {
// Only process clusters taxonomy
if ($taxonomy !== 'clusters') {
return;
}
global $wpdb;
// Get all cluster IDs that have terms in this taxonomy update
$cluster_ids = [];
foreach ($terms as $term) {
if (is_numeric($term)) {
$term_id = $term;
} else {
$term_obj = get_term_by('slug', $term, 'clusters');
$term_id = $term_obj ? $term_obj->term_id : null;
}
if ($term_id) {
// Find cluster that has this term_id
$cluster_id = $wpdb->get_var($wpdb->prepare("
SELECT id FROM {$wpdb->prefix}igny8_clusters
WHERE cluster_term_id = %d
", $term_id));
if ($cluster_id) {
$cluster_ids[] = $cluster_id;
}
}
}
// Update metrics for all affected clusters
foreach (array_unique($cluster_ids) as $cluster_id) {
igny8_update_cluster_metrics($cluster_id);
}
}
// Hook registration for automatic cluster metrics updates when content is associated/disassociated with cluster terms
// Hook moved to sync-hooks.php
/**
* Update task metrics including word count and meta sync
*/
function igny8_update_task_metrics($task_id) {
global $wpdb;
$post_id = $wpdb->get_var($wpdb->prepare("SELECT assigned_post_id FROM {$wpdb->prefix}igny8_tasks WHERE id = %d", $task_id));
if ($post_id) {
$content = get_post_field('post_content', $post_id);
$word_count = str_word_count(strip_tags($content));
update_post_meta($post_id, '_igny8_word_count', $word_count);
$wpdb->update("{$wpdb->prefix}igny8_tasks", ['word_count' => $word_count], ['id' => $task_id]);
}
}