Files
igny8/igny8-wp-plugin/sync/igny8-to-wp.php
2025-12-01 02:22:02 +00:00

1248 lines
45 KiB
PHP

<?php
/**
* IGNY8 → WordPress Synchronization
*
* Handles creating WordPress posts from IGNY8 content
*
* @package Igny8Bridge
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Determine WordPress post type for IGNY8 task
*
* @param array $content_data Task data
* @return string
*/
function igny8_resolve_post_type_for_task($content_data) {
$content_type = $content_data['content_type'] ?? $content_data['post_type'] ?? 'post';
$post_type_map = array(
'post' => 'post',
'page' => 'page',
'product' => 'product',
'article' => 'post',
'blog' => 'post'
);
$post_type = isset($post_type_map[$content_type]) ? $post_type_map[$content_type] : $content_type;
$post_type = apply_filters('igny8_post_type_for_task', $post_type, $content_data);
if (!post_type_exists($post_type)) {
$post_type = 'post';
}
return $post_type;
}
/**
* Cache writer brief for a task
*
* @param int $task_id IGNY8 task ID
* @param int $post_id WordPress post ID
* @param Igny8API|null $api Optional API client
*/
function igny8_cache_task_brief($task_id, $post_id, $api = null) {
if (!$task_id || !$post_id) {
return;
}
$api = $api ?: new Igny8API();
if (!$api->is_authenticated()) {
return;
}
$response = $api->get("/writer/tasks/{$task_id}/brief/");
if ($response && !empty($response['success']) && !empty($response['data'])) {
update_post_meta($post_id, '_igny8_task_brief', $response['data']);
update_post_meta($post_id, '_igny8_brief_cached_at', current_time('mysql'));
}
}
/**
* Create WordPress post from IGNY8 task/content
*
* @param array $content_data Content data from IGNY8
* @param array $allowed_post_types Post types allowed to be created automatically
* @return int|WP_Error WordPress post ID or error
*/
function igny8_create_wordpress_post_from_task($content_data, $allowed_post_types = array()) {
// Get site information for logging
$site_id = get_option('igny8_site_id', 'unknown');
$site_domain = parse_url(home_url(), PHP_URL_HOST);
$log_prefix = "[{$site_id}-{$site_domain}]";
Igny8_Logger::separator("{$log_prefix} 🎯 CREATE WORDPRESS POST FROM IGNY8");
Igny8_Logger::info("{$log_prefix} Content ID: " . ($content_data['content_id'] ?? 'N/A'));
Igny8_Logger::info("{$log_prefix} Task ID: " . ($content_data['task_id'] ?? 'N/A'));
Igny8_Logger::info("{$log_prefix} Title: " . ($content_data['title'] ?? 'N/A'));
Igny8_Logger::separator();
$api = new Igny8API();
if (!$api->is_authenticated()) {
Igny8_Logger::error("{$log_prefix} ❌ NOT AUTHENTICATED - Aborting post creation");
return new WP_Error('igny8_not_authenticated', 'IGNY8 API not authenticated');
}
Igny8_Logger::info("{$log_prefix} ✅ API authenticated");
$post_type = igny8_resolve_post_type_for_task($content_data);
Igny8_Logger::info("{$log_prefix} STEP 1: Resolved post type: {$post_type}");
if (!empty($allowed_post_types) && !in_array($post_type, $allowed_post_types, true)) {
return new WP_Error('igny8_post_type_disabled', sprintf('Post type %s is disabled for automation', $post_type));
}
// Prepare post data
// Stage 1 Schema: accept content_html (new) or content (legacy fallback)
$content_html = $content_data['content_html'] ?? $content_data['content'] ?? '';
// Map author
$author_id = igny8_map_content_author($content_data);
// Prepare excerpt
$excerpt = $content_data['excerpt'] ?? $content_data['brief'] ?? '';
if (empty($excerpt) && !empty($content_html)) {
$excerpt = wp_trim_excerpt($content_html);
}
$post_data = array(
'post_title' => sanitize_text_field($content_data['title'] ?? 'Untitled'),
'post_content' => wp_kses_post($content_html),
'post_excerpt' => sanitize_text_field($excerpt),
'post_status' => igny8_map_igny8_status_to_wp($content_data['status'] ?? 'draft'),
'post_type' => $post_type,
'post_author' => $author_id,
'meta_input' => array()
);
// Set publication date if provided
if (!empty($content_data['published_at'])) {
$post_data['post_date'] = $content_data['published_at'];
$post_data['post_date_gmt'] = get_gmt_from_date($content_data['published_at']);
}
// Add IGNY8 meta
if (!empty($content_data['task_id'])) {
$post_data['meta_input']['_igny8_task_id'] = $content_data['task_id'];
}
if (!empty($content_data['content_id'])) {
$post_data['meta_input']['_igny8_content_id'] = $content_data['content_id'];
}
if (!empty($content_data['cluster_id'])) {
$post_data['meta_input']['_igny8_cluster_id'] = $content_data['cluster_id'];
}
// Stage 1: New meta fields
if (!empty($content_data['content_type'])) {
$post_data['meta_input']['_igny8_content_type'] = $content_data['content_type'];
}
if (!empty($content_data['content_structure'])) {
$post_data['meta_input']['_igny8_content_structure'] = $content_data['content_structure'];
}
// Store source field for tracking content origin (Stage 1)
if (!empty($content_data['source'])) {
$post_data['meta_input']['_igny8_source'] = $content_data['source'];
}
if (!empty($content_data['sector_id'])) {
$post_data['meta_input']['_igny8_sector_id'] = $content_data['sector_id'];
}
if (!empty($content_data['keyword_ids'])) {
$post_data['meta_input']['_igny8_keyword_ids'] = $content_data['keyword_ids'];
}
// Create post
$post_id = wp_insert_post($post_data);
if (is_wp_error($post_id)) {
error_log("IGNY8: Failed to create WordPress post: " . $post_id->get_error_message());
return $post_id;
}
// Handle SEO metadata
igny8_import_seo_metadata($post_id, $content_data);
// Handle featured image
igny8_import_featured_image($post_id, $content_data);
// Handle categories and tags
igny8_import_taxonomies($post_id, $content_data);
// Import and process content images
igny8_import_content_images($post_id, $content_html);
// Assign taxonomies if cluster/sector IDs exist
if (!empty($content_data['cluster_id'])) {
// Find cluster term
$cluster_terms = get_terms(array(
'taxonomy' => 'igny8_clusters',
'meta_key' => '_igny8_cluster_id',
'meta_value' => $content_data['cluster_id'],
'hide_empty' => false
));
if (!is_wp_error($cluster_terms) && !empty($cluster_terms)) {
wp_set_post_terms($post_id, array($cluster_terms[0]->term_id), 'igny8_clusters');
}
}
if (!empty($content_data['sector_id'])) {
// Find sector term
$sector_terms = get_terms(array(
'taxonomy' => 'igny8_sectors',
'meta_key' => '_igny8_sector_id',
'meta_value' => $content_data['sector_id'],
'hide_empty' => false
));
if (!is_wp_error($sector_terms) && !empty($sector_terms)) {
wp_set_post_terms($post_id, array($sector_terms[0]->term_id), 'igny8_sectors');
}
}
// Handle categories
if (!empty($content_data['categories'])) {
Igny8_Logger::info("{$log_prefix} STEP: Processing " . count($content_data['categories']) . " categories");
$category_ids = igny8_process_categories($content_data['categories'], $post_id);
if (!empty($category_ids)) {
wp_set_post_terms($post_id, $category_ids, 'category');
Igny8_Logger::info("{$log_prefix} ✅ Assigned " . count($category_ids) . " categories to post {$post_id}");
} else {
Igny8_Logger::warning("{$log_prefix} ⚠️ No category IDs returned from processing");
}
} else {
Igny8_Logger::warning("{$log_prefix} ⚠️ No categories in content_data");
}
// Handle tags
if (!empty($content_data['tags'])) {
Igny8_Logger::info("{$log_prefix} STEP: Processing " . count($content_data['tags']) . " tags");
$tag_ids = igny8_process_tags($content_data['tags'], $post_id);
if (!empty($tag_ids)) {
wp_set_post_terms($post_id, $tag_ids, 'post_tag');
Igny8_Logger::info("{$log_prefix} ✅ Assigned " . count($tag_ids) . " tags to post {$post_id}");
} else {
Igny8_Logger::warning("{$log_prefix} ⚠️ No tag IDs returned from processing");
}
} else {
Igny8_Logger::warning("{$log_prefix} ⚠️ No tags in content_data");
}
// Handle featured image
if (!empty($content_data['featured_image'])) {
Igny8_Logger::info("{$log_prefix} STEP: Setting featured image from featured_image field");
igny8_set_featured_image($post_id, $content_data['featured_image']);
} elseif (!empty($content_data['featured_image_url'])) {
Igny8_Logger::info("{$log_prefix} STEP: Setting featured image from URL: " . $content_data['featured_image_url']);
igny8_set_featured_image($post_id, $content_data['featured_image_url']);
} else {
Igny8_Logger::warning("{$log_prefix} ⚠️ No featured image in content_data");
}
// Handle meta title and meta description (SEO)
if (!empty($content_data['meta_title'])) {
Igny8_Logger::info("{$log_prefix} STEP: Setting SEO meta title: " . $content_data['meta_title']);
update_post_meta($post_id, '_yoast_wpseo_title', $content_data['meta_title']);
update_post_meta($post_id, '_seopress_titles_title', $content_data['meta_title']);
update_post_meta($post_id, '_aioseo_title', $content_data['meta_title']);
// Generic meta
update_post_meta($post_id, '_igny8_meta_title', $content_data['meta_title']);
} elseif (!empty($content_data['seo_title'])) {
Igny8_Logger::info("{$log_prefix} STEP: Setting SEO meta title from seo_title: " . $content_data['seo_title']);
update_post_meta($post_id, '_yoast_wpseo_title', $content_data['seo_title']);
update_post_meta($post_id, '_seopress_titles_title', $content_data['seo_title']);
update_post_meta($post_id, '_aioseo_title', $content_data['seo_title']);
update_post_meta($post_id, '_igny8_meta_title', $content_data['seo_title']);
} else {
Igny8_Logger::warning("{$log_prefix} ⚠️ No SEO title in content_data");
}
if (!empty($content_data['meta_description'])) {
Igny8_Logger::info("{$log_prefix} STEP: Setting SEO meta description");
update_post_meta($post_id, '_yoast_wpseo_metadesc', $content_data['meta_description']);
update_post_meta($post_id, '_seopress_titles_desc', $content_data['meta_description']);
update_post_meta($post_id, '_aioseo_description', $content_data['meta_description']);
// Generic meta
update_post_meta($post_id, '_igny8_meta_description', $content_data['meta_description']);
} elseif (!empty($content_data['seo_description'])) {
Igny8_Logger::info("{$log_prefix} STEP: Setting SEO meta description from seo_description");
update_post_meta($post_id, '_yoast_wpseo_metadesc', $content_data['seo_description']);
update_post_meta($post_id, '_seopress_titles_desc', $content_data['seo_description']);
update_post_meta($post_id, '_aioseo_description', $content_data['seo_description']);
update_post_meta($post_id, '_igny8_meta_description', $content_data['seo_description']);
} else {
Igny8_Logger::warning("{$log_prefix} ⚠️ No SEO description in content_data");
}
// Handle gallery images
if (!empty($content_data['gallery_images'])) {
Igny8_Logger::info("{$log_prefix} STEP: Setting gallery with " . count($content_data['gallery_images']) . " images");
igny8_set_gallery_images($post_id, $content_data['gallery_images']);
}
// Get the actual WordPress post status (after creation)
$created_post = get_post($post_id);
$wp_status = $created_post ? $created_post->post_status : 'draft';
// Store WordPress status in meta for IGNY8 to read
update_post_meta($post_id, '_igny8_wordpress_status', $wp_status);
// Map WordPress status back to IGNY8 status
$igny8_status = igny8_map_wp_status_to_igny8($wp_status);
// Update IGNY8 task with WordPress post ID, URL, and status
if (!empty($content_data['task_id'])) {
$update_data = array(
'assigned_post_id' => $post_id,
'post_url' => get_permalink($post_id),
'wordpress_status' => $wp_status, // WordPress actual status (publish/pending/draft)
'status' => $igny8_status, // IGNY8 mapped status (completed/pending/draft)
'synced_at' => current_time('mysql'),
'post_type' => $post_type, // WordPress post type
'content_type' => $content_type // IGNY8 content type
);
// Include content_id if provided
if (!empty($content_data['content_id'])) {
$update_data['content_id'] = $content_data['content_id'];
}
$response = $api->put("/writer/tasks/{$content_data['task_id']}/", $update_data);
if ($response['success']) {
Igny8_Logger::info("{$log_prefix} ✅ Updated IGNY8 task {$content_data['task_id']} with WordPress post {$post_id} (status: {$wp_status})");
} else {
Igny8_Logger::error("{$log_prefix} ❌ Failed to update IGNY8 task: " . ($response['error'] ?? 'Unknown error'));
}
}
// Store content_id if provided (for IGNY8 to query)
if (!empty($content_data['content_id'])) {
update_post_meta($post_id, '_igny8_content_id', $content_data['content_id']);
}
// Send status webhook to IGNY8
igny8_send_status_webhook($post_id, $content_data, $wp_status);
Igny8_Logger::separator("{$log_prefix} 🎉 POST CREATION COMPLETED SUCCESSFULLY");
Igny8_Logger::info("{$log_prefix} WordPress Post ID: {$post_id}");
Igny8_Logger::info("{$log_prefix} Post URL: " . get_permalink($post_id));
Igny8_Logger::info("{$log_prefix} Post Status: {$wp_status}");
Igny8_Logger::separator();
return $post_id;
}
/**
* Map IGNY8 task status to WordPress post status
*
* @param string $igny8_status IGNY8 task status
* @return string WordPress post status
*/
function igny8_map_igny8_status_to_wp($igny8_status) {
$status_map = array(
'completed' => 'publish',
'draft' => 'draft',
'pending' => 'pending',
'scheduled' => 'future',
'archived' => 'trash'
);
return isset($status_map[$igny8_status]) ? $status_map[$igny8_status] : 'draft';
}
/**
* Sync IGNY8 tasks to WordPress posts
* Fetches tasks from IGNY8 and creates/updates WordPress posts
*
* @param array $filters Optional filters (status, cluster_id, etc.)
* @return array Sync results
*/
function igny8_sync_igny8_tasks_to_wp($filters = array()) {
// Skip if connection is disabled
if (!igny8_is_connection_enabled()) {
return array('success' => false, 'error' => 'Connection disabled', 'disabled' => true);
}
$api = new Igny8API();
if (!$api->is_authenticated()) {
return array('success' => false, 'error' => 'Not authenticated');
}
if (function_exists('igny8_is_module_enabled') && !igny8_is_module_enabled('writer')) {
return array('success' => true, 'created' => 0, 'updated' => 0, 'failed' => 0, 'skipped' => 0, 'total' => 0, 'disabled' => true);
}
$site_id = get_option('igny8_site_id');
if (!$site_id) {
return array('success' => false, 'error' => 'Site ID not configured');
}
$enabled_post_types = function_exists('igny8_get_enabled_post_types') ? igny8_get_enabled_post_types() : array('post', 'page');
// Build endpoint with filters
$endpoint = '/writer/tasks/';
$query_params = array();
$query_params[] = 'site_id=' . intval($site_id);
if (!empty($filters['status'])) {
$query_params[] = 'status=' . urlencode($filters['status']);
}
if (!empty($filters['cluster_id'])) {
$query_params[] = 'cluster_id=' . intval($filters['cluster_id']);
}
if (!empty($query_params)) {
$endpoint .= '?' . implode('&', $query_params);
}
// Get tasks from IGNY8
$response = $api->get($endpoint);
if (!$response['success']) {
return array('success' => false, 'error' => $response['error'] ?? 'Unknown error');
}
$tasks = $response['data']['results'] ?? $response['data'] ?? $response['results'] ?? array();
$created = 0;
$updated = 0;
$failed = 0;
$skipped = 0;
foreach ($tasks as $task) {
// Check if post already exists
$existing_posts = get_posts(array(
'meta_key' => '_igny8_task_id',
'meta_value' => $task['id'],
'post_type' => 'any',
'posts_per_page' => 1
));
if (!empty($existing_posts)) {
// Update existing post
$post_id = $existing_posts[0]->ID;
$update_data = array(
'ID' => $post_id,
'post_title' => $task['title'] ?? get_the_title($post_id),
'post_status' => igny8_map_igny8_status_to_wp($task['status'] ?? 'draft')
);
// Stage 1 Schema: accept content_html or content
$content_html = $task['content_html'] ?? $task['content'] ?? '';
if (!empty($content_html)) {
$update_data['post_content'] = $content_html;
}
$result = wp_update_post($update_data);
// Update categories, tags, images, and meta
if ($result && !is_wp_error($result)) {
// Update categories
if (!empty($task['categories'])) {
$category_ids = igny8_process_categories($task['categories'], $post_id);
if (!empty($category_ids)) {
wp_set_post_terms($post_id, $category_ids, 'category');
}
}
// Update tags
if (!empty($task['tags'])) {
$tag_ids = igny8_process_tags($task['tags'], $post_id);
if (!empty($tag_ids)) {
wp_set_post_terms($post_id, $tag_ids, 'post_tag');
}
}
// Update featured image
if (!empty($task['featured_image']) || !empty($task['featured_media'])) {
igny8_set_featured_image($post_id, $task['featured_image'] ?? $task['featured_media']);
}
// Update gallery
if (!empty($task['gallery_images']) || !empty($task['images'])) {
igny8_set_image_gallery($post_id, $task['gallery_images'] ?? $task['images']);
}
// Update meta title and description
if (!empty($task['meta_title']) || !empty($task['seo_title'])) {
$meta_title = $task['meta_title'] ?? $task['seo_title'];
update_post_meta($post_id, '_yoast_wpseo_title', $meta_title);
update_post_meta($post_id, '_seopress_titles_title', $meta_title);
update_post_meta($post_id, '_aioseo_title', $meta_title);
update_post_meta($post_id, '_igny8_meta_title', $meta_title);
}
if (!empty($task['meta_description']) || !empty($task['seo_description'])) {
$meta_desc = $task['meta_description'] ?? $task['seo_description'];
update_post_meta($post_id, '_yoast_wpseo_metadesc', $meta_desc);
update_post_meta($post_id, '_seopress_titles_desc', $meta_desc);
update_post_meta($post_id, '_aioseo_description', $meta_desc);
update_post_meta($post_id, '_igny8_meta_description', $meta_desc);
}
}
if ($result && !is_wp_error($result)) {
igny8_cache_task_brief($task['id'], $post_id, $api);
$updated++;
} else {
$failed++;
}
} else {
// Create new post
$task_post_type = igny8_resolve_post_type_for_task($task);
if (!empty($enabled_post_types) && !in_array($task_post_type, $enabled_post_types, true)) {
$skipped++;
continue;
}
// Stage 1 Schema: use content_html with fallback
$content_html = $task['content_html'] ?? $task['content'] ?? '';
$content_data = array(
'task_id' => $task['id'],
'content_id' => $task['content_id'] ?? null,
'title' => $task['title'] ?? 'Untitled',
'content_html' => $content_html,
'content' => $content_html, // Keep for backward compatibility in function
'status' => $task['status'] ?? 'draft',
'cluster_id' => $task['cluster_id'] ?? null,
'sector_id' => $task['sector_id'] ?? null,
'keyword_ids' => $task['keyword_ids'] ?? array(),
'content_type' => $task['content_type'] ?? $task['post_type'] ?? 'post',
'content_structure' => $task['content_structure'] ?? 'article',
'post_type' => $task['post_type'] ?? null, // Keep for backward compatibility
'categories' => $task['categories'] ?? array(),
'tags' => $task['tags'] ?? array(),
'featured_image' => $task['featured_image'] ?? $task['featured_media'] ?? null,
'gallery_images' => $task['gallery_images'] ?? $task['images'] ?? array(),
'meta_title' => $task['meta_title'] ?? $task['seo_title'] ?? null,
'meta_description' => $task['meta_description'] ?? $task['seo_description'] ?? null
);
$post_id = igny8_create_wordpress_post_from_task($content_data, $enabled_post_types);
if (is_wp_error($post_id)) {
if ($post_id->get_error_code() === 'igny8_post_type_disabled') {
$skipped++;
} else {
$failed++;
}
} elseif ($post_id) {
igny8_cache_task_brief($task['id'], $post_id, $api);
$created++;
} else {
$failed++;
}
}
}
return array(
'success' => true,
'created' => $created,
'updated' => $updated,
'failed' => $failed,
'skipped' => $skipped,
'total' => count($tasks)
);
}
/**
* Handle webhook from IGNY8 (when content is published from IGNY8)
* This can be called via REST API endpoint or scheduled sync
*
* @param array $webhook_data Webhook data from IGNY8
* @return int|false WordPress post ID or false on failure
*/
function igny8_handle_igny8_webhook($webhook_data) {
if (empty($webhook_data['task_id'])) {
return false;
}
$api = new Igny8API();
// Get full task data from IGNY8
$task_response = $api->get("/writer/tasks/{$webhook_data['task_id']}/");
if (!$task_response['success']) {
return false;
}
$task = $task_response['data'];
// Prepare content data
// Stage 1 Schema: use content_html with fallback to content
$content_html = $task['content_html'] ?? $task['content'] ?? '';
$content_data = array(
'task_id' => $task['id'],
'content_id' => $task['content_id'] ?? null,
'title' => $task['title'] ?? 'Untitled',
'content_html' => $content_html,
'content' => $content_html, // Keep for backward compatibility
'status' => $task['status'] ?? 'draft',
'cluster_id' => $task['cluster_id'] ?? null,
'sector_id' => $task['sector_id'] ?? null,
'keyword_ids' => $task['keyword_ids'] ?? array(),
'content_type' => $task['content_type'] ?? 'post',
'content_structure' => $task['content_structure'] ?? 'article',
'post_type' => $task['post_type'] ?? $task['content_type'] ?? 'post'
);
return igny8_create_wordpress_post_from_task($content_data);
}
/**
* Process categories from IGNY8
*
* @param array $categories Category data (IDs, names, or slugs)
* @param int $post_id Post ID
* @return array Category term IDs
*/
function igny8_process_categories($categories, $post_id) {
$category_ids = array();
foreach ($categories as $category) {
$term_id = null;
// If it's an ID
if (is_numeric($category)) {
$term = get_term($category, 'category');
if ($term && !is_wp_error($term)) {
$term_id = $term->term_id;
}
}
// If it's an array with name/slug
elseif (is_array($category)) {
$name = $category['name'] ?? $category['slug'] ?? null;
$slug = $category['slug'] ?? sanitize_title($name);
if ($name) {
// Try to find existing term
$term = get_term_by('slug', $slug, 'category');
if (!$term) {
$term = get_term_by('name', $name, 'category');
}
// Create if doesn't exist
if (!$term || is_wp_error($term)) {
$term_result = wp_insert_term($name, 'category', array('slug' => $slug));
if (!is_wp_error($term_result)) {
$term_id = $term_result['term_id'];
}
} else {
$term_id = $term->term_id;
}
}
}
// If it's a string (name or slug)
elseif (is_string($category)) {
$term = get_term_by('slug', $category, 'category');
if (!$term) {
$term = get_term_by('name', $category, 'category');
}
if ($term && !is_wp_error($term)) {
$term_id = $term->term_id;
} else {
// Create new category
$term_result = wp_insert_term($category, 'category');
if (!is_wp_error($term_result)) {
$term_id = $term_result['term_id'];
}
}
}
if ($term_id) {
$category_ids[] = $term_id;
}
}
return array_unique($category_ids);
}
/**
* Process tags from IGNY8
*
* @param array $tags Tag data (IDs, names, or slugs)
* @param int $post_id Post ID
* @return array Tag term IDs
*/
function igny8_process_tags($tags, $post_id) {
$tag_ids = array();
foreach ($tags as $tag) {
$term_id = null;
// If it's an ID
if (is_numeric($tag)) {
$term = get_term($tag, 'post_tag');
if ($term && !is_wp_error($term)) {
$term_id = $term->term_id;
}
}
// If it's an array with name/slug
elseif (is_array($tag)) {
$name = $tag['name'] ?? $tag['slug'] ?? null;
$slug = $tag['slug'] ?? sanitize_title($name);
if ($name) {
// Try to find existing term
$term = get_term_by('slug', $slug, 'post_tag');
if (!$term) {
$term = get_term_by('name', $name, 'post_tag');
}
// Create if doesn't exist
if (!$term || is_wp_error($term)) {
$term_result = wp_insert_term($name, 'post_tag', array('slug' => $slug));
if (!is_wp_error($term_result)) {
$term_id = $term_result['term_id'];
}
} else {
$term_id = $term->term_id;
}
}
}
// If it's a string (name or slug)
elseif (is_string($tag)) {
$term = get_term_by('slug', $tag, 'post_tag');
if (!$term) {
$term = get_term_by('name', $tag, 'post_tag');
}
if ($term && !is_wp_error($term)) {
$term_id = $term->term_id;
} else {
// Create new tag
$term_result = wp_insert_term($tag, 'post_tag');
if (!is_wp_error($term_result)) {
$term_id = $term_result['term_id'];
}
}
}
if ($term_id) {
$tag_ids[] = $term_id;
}
}
return array_unique($tag_ids);
}
/**
* Set featured image for post
*
* @param int $post_id Post ID
* @param string|array $image_data Image URL or array with image data
* @return int|false Attachment ID or false on failure
*/
function igny8_set_featured_image($post_id, $image_data) {
$image_url = is_array($image_data) ? ($image_data['url'] ?? $image_data['src'] ?? '') : $image_data;
if (empty($image_url)) {
return false;
}
// Check if image already exists
$attachment_id = igny8_get_attachment_by_url($image_url);
if (!$attachment_id) {
// Download and attach image
$attachment_id = igny8_import_image($image_url, $post_id);
}
if ($attachment_id) {
set_post_thumbnail($post_id, $attachment_id);
return $attachment_id;
}
return false;
}
/**
* Set image gallery for post (1-5 images)
*
* @param int $post_id Post ID
* @param array $gallery_images Array of image URLs or image data
* @return array Attachment IDs
*/
function igny8_set_image_gallery($post_id, $gallery_images) {
$attachment_ids = array();
// Limit to 5 images
$gallery_images = array_slice($gallery_images, 0, 5);
foreach ($gallery_images as $image_data) {
$image_url = is_array($image_data) ? ($image_data['url'] ?? $image_data['src'] ?? '') : $image_data;
if (empty($image_url)) {
continue;
}
// Check if image already exists
$attachment_id = igny8_get_attachment_by_url($image_url);
if (!$attachment_id) {
// Download and attach image
$attachment_id = igny8_import_image($image_url, $post_id);
}
if ($attachment_id) {
$attachment_ids[] = $attachment_id;
}
}
// Store gallery as post meta (WordPress native)
if (!empty($attachment_ids)) {
update_post_meta($post_id, '_igny8_gallery_images', $attachment_ids);
// Also store in format compatible with plugins
update_post_meta($post_id, '_product_image_gallery', implode(',', $attachment_ids)); // WooCommerce
update_post_meta($post_id, '_gallery_images', $attachment_ids); // Generic
}
return $attachment_ids;
}
/**
* Get attachment ID by image URL
*
* @param string $image_url Image URL
* @return int|false Attachment ID or false
*/
function igny8_get_attachment_by_url($image_url) {
global $wpdb;
$attachment_id = $wpdb->get_var($wpdb->prepare(
"SELECT ID FROM {$wpdb->posts} WHERE guid = %s AND post_type = 'attachment'",
$image_url
));
return $attachment_id ? intval($attachment_id) : false;
}
/**
* Import image from URL and attach to post
*
* @param string $image_url Image URL
* @param int $post_id Post ID to attach to
* @return int|false Attachment ID or false on failure
*/
function igny8_import_image($image_url, $post_id) {
require_once(ABSPATH . 'wp-admin/includes/image.php');
require_once(ABSPATH . 'wp-admin/includes/file.php');
require_once(ABSPATH . 'wp-admin/includes/media.php');
// Download image
$tmp = download_url($image_url);
if (is_wp_error($tmp)) {
error_log("IGNY8: Failed to download image {$image_url}: " . $tmp->get_error_message());
return false;
}
// Get file extension
$file_array = array(
'name' => basename(parse_url($image_url, PHP_URL_PATH)),
'tmp_name' => $tmp
);
// Upload to WordPress media library
$attachment_id = media_handle_sideload($file_array, $post_id);
// Clean up temp file
@unlink($tmp);
if (is_wp_error($attachment_id)) {
error_log("IGNY8: Failed to import image {$image_url}: " . $attachment_id->get_error_message());
return false;
}
return $attachment_id;
}
/**
* Map IGNY8 author to WordPress user
*
* @param array $content_data Content data from IGNY8
* @return int WordPress user ID
*/
function igny8_map_content_author($content_data) {
$author_email = $content_data['author_email'] ?? $content_data['author'] ?? '';
if (empty($author_email)) {
return 1; // Default to admin user
}
// Try to find existing user by email
$user = get_user_by('email', $author_email);
if ($user) {
return $user->ID;
}
// Create new user if enabled and author name provided
$author_name = $content_data['author_name'] ?? $content_data['author_display_name'] ?? '';
if (!empty($author_name) && apply_filters('igny8_auto_create_authors', true)) {
$user_data = array(
'user_login' => sanitize_user($author_email),
'user_email' => $author_email,
'display_name' => sanitize_text_field($author_name),
'role' => apply_filters('igny8_default_author_role', 'author')
);
$user_id = wp_insert_user($user_data);
if (!is_wp_error($user_id)) {
error_log("IGNY8: Created new author user: {$author_email}");
return $user_id;
}
}
return 1; // Fallback to admin
}
/**
* Import SEO metadata for post
*
* @param int $post_id WordPress post ID
* @param array $content_data Content data from IGNY8
*/
function igny8_import_seo_metadata($post_id, $content_data) {
// SEO title
if (!empty($content_data['seo_title'])) {
update_post_meta($post_id, '_igny8_seo_title', sanitize_text_field($content_data['seo_title']));
// For Yoast SEO compatibility
if (defined('WPSEO_VERSION')) {
update_post_meta($post_id, '_yoast_wpseo_title', sanitize_text_field($content_data['seo_title']));
}
}
// SEO description
if (!empty($content_data['seo_description'])) {
update_post_meta($post_id, '_igny8_seo_description', sanitize_text_field($content_data['seo_description']));
// For Yoast SEO compatibility
if (defined('WPSEO_VERSION')) {
update_post_meta($post_id, '_yoast_wpseo_metadesc', sanitize_text_field($content_data['seo_description']));
}
}
// Focus keywords
if (!empty($content_data['focus_keywords'])) {
update_post_meta($post_id, '_igny8_focus_keywords', $content_data['focus_keywords']);
// For Yoast SEO compatibility
if (defined('WPSEO_VERSION') && is_array($content_data['focus_keywords'])) {
update_post_meta($post_id, '_yoast_wpseo_focuskw', $content_data['focus_keywords'][0]);
}
}
}
/**
* Import featured image for post
*
* @param int $post_id WordPress post ID
* @param array $content_data Content data from IGNY8
*/
function igny8_import_featured_image($post_id, $content_data) {
$featured_image_url = $content_data['featured_image_url'] ?? $content_data['featured_image'] ?? '';
if (empty($featured_image_url)) {
return;
}
// Check if URL is valid
if (!filter_var($featured_image_url, FILTER_VALIDATE_URL)) {
error_log("IGNY8: Invalid featured image URL: {$featured_image_url}");
return;
}
// Import image
$attachment_id = igny8_import_image($featured_image_url, $post_id);
if ($attachment_id) {
set_post_thumbnail($post_id, $attachment_id);
update_post_meta($post_id, '_igny8_featured_image_imported', current_time('mysql'));
error_log("IGNY8: Featured image imported for post {$post_id}: attachment {$attachment_id}");
}
}
/**
* Import taxonomies (categories and tags) for post
*
* @param int $post_id WordPress post ID
* @param array $content_data Content data from IGNY8
*/
function igny8_import_taxonomies($post_id, $content_data) {
// Handle categories from sectors
if (!empty($content_data['sectors']) && is_array($content_data['sectors'])) {
$category_ids = array();
foreach ($content_data['sectors'] as $sector) {
$sector_name = is_array($sector) ? $sector['name'] : $sector;
if (empty($sector_name)) {
continue;
}
// Find or create category
$term = get_term_by('name', $sector_name, 'category');
if (!$term) {
$term_result = wp_insert_term($sector_name, 'category');
if (!is_wp_error($term_result)) {
$category_ids[] = $term_result['term_id'];
// Store IGNY8 sector ID if provided
if (is_array($sector) && !empty($sector['id'])) {
update_term_meta($term_result['term_id'], '_igny8_sector_id', $sector['id']);
}
}
} else {
$category_ids[] = $term->term_id;
}
}
if (!empty($category_ids)) {
wp_set_post_categories($post_id, $category_ids);
}
}
// Handle tags from clusters
if (!empty($content_data['clusters']) && is_array($content_data['clusters'])) {
$tag_names = array();
foreach ($content_data['clusters'] as $cluster) {
$cluster_name = is_array($cluster) ? $cluster['name'] : $cluster;
if (!empty($cluster_name)) {
$tag_names[] = $cluster_name;
}
}
if (!empty($tag_names)) {
wp_set_post_tags($post_id, $tag_names);
}
}
// Handle custom tags if provided
if (!empty($content_data['tags']) && is_array($content_data['tags'])) {
wp_set_post_tags($post_id, $content_data['tags'], true); // Append to existing
}
}
/**
* Import and process images in content
*
* @param int $post_id WordPress post ID
* @param string $content_html HTML content
*/
function igny8_import_content_images($post_id, $content_html) {
if (empty($content_html)) {
return;
}
// Find all images in content
preg_match_all('/<img[^>]+src="([^"]+)"[^>]*>/i', $content_html, $matches);
if (empty($matches[1])) {
return;
}
$imported_images = array();
foreach ($matches[1] as $image_url) {
// Skip if already a WordPress URL
if (strpos($image_url, site_url()) === 0) {
continue;
}
// Skip invalid URLs
if (!filter_var($image_url, FILTER_VALIDATE_URL)) {
continue;
}
// Import image
$attachment_id = igny8_import_image($image_url, $post_id);
if ($attachment_id) {
$new_url = wp_get_attachment_url($attachment_id);
if ($new_url) {
// Replace URL in content
$content_html = str_replace($image_url, $new_url, $content_html);
$imported_images[] = $attachment_id;
}
}
}
// Update post content with new image URLs
if (!empty($imported_images)) {
wp_update_post(array(
'ID' => $post_id,
'post_content' => $content_html
));
update_post_meta($post_id, '_igny8_imported_images', $imported_images);
update_post_meta($post_id, '_igny8_images_imported_at', current_time('mysql'));
error_log("IGNY8: Imported " . count($imported_images) . " content images for post {$post_id}");
}
}
/**
* Scheduled sync from IGNY8 to WordPress
* Fetches new/updated tasks from IGNY8 and creates/updates WordPress posts
*/
function igny8_cron_sync_from_igny8() {
// Skip if connection is disabled
if (!igny8_is_connection_enabled()) {
error_log('IGNY8: Connection disabled, skipping sync from IGNY8');
return;
}
$site_id = get_option('igny8_site_id');
if (!$site_id) {
error_log('IGNY8: Site ID not set, skipping sync from IGNY8');
return;
}
if (function_exists('igny8_is_module_enabled') && !igny8_is_module_enabled('writer')) {
error_log('IGNY8: Writer module disabled, skipping sync from IGNY8');
return;
}
// Get last sync time
$last_sync = get_option('igny8_last_sync_from_igny8', 0);
// Sync only completed/published tasks
$filters = array(
'status' => 'completed'
);
// If we have a last sync time, we could filter by updated date
// For now, sync all completed tasks (API should handle deduplication)
$result = igny8_sync_igny8_tasks_to_wp($filters);
if ($result['success']) {
update_option('igny8_last_sync_from_igny8', time());
update_option('igny8_last_writer_sync', current_time('timestamp'));
error_log(sprintf(
'IGNY8: Synced from IGNY8 - Created %d posts, updated %d posts, %d failed, %d skipped',
$result['created'],
$result['updated'],
$result['failed'],
$result['skipped'] ?? 0
));
} else {
error_log('IGNY8: Failed to sync from IGNY8: ' . ($result['error'] ?? 'Unknown error'));
}
}
/**
* Send status webhook to IGNY8 backend
* Notifies IGNY8 when WordPress post status changes
*
* @param int $post_id WordPress post ID
* @param array $content_data Content data containing content_id
* @param string $post_status WordPress post status
*/
function igny8_send_status_webhook($post_id, $content_data, $post_status) {
// Get site information for logging
$site_id = get_option('igny8_site_id', 'unknown');
$site_domain = parse_url(home_url(), PHP_URL_HOST);
$log_prefix = "[{$site_id}-{$site_domain}]";
Igny8_Logger::separator("{$log_prefix} 📤 SENDING STATUS WEBHOOK TO IGNY8");
// Only send webhook if connection is enabled
if (!igny8_is_connection_enabled()) {
Igny8_Logger::warning("{$log_prefix} ⚠️ Connection not enabled, skipping webhook", 'webhooks');
return;
}
// Get required data
$content_id = $content_data['content_id'] ?? get_post_meta($post_id, '_igny8_content_id', true);
if (!$content_id) {
Igny8_Logger::error("{$log_prefix} ❌ Cannot send status webhook - no content_id", 'webhooks');
return;
}
Igny8_Logger::info("{$log_prefix} Content ID: {$content_id}", 'webhooks');
Igny8_Logger::info("{$log_prefix} Post ID: {$post_id}", 'webhooks');
Igny8_Logger::info("{$log_prefix} Post Status: {$post_status}", 'webhooks');
// Get API endpoint from settings
$api = new Igny8API();
$api_base = $api->get_api_base();
if (!$api_base) {
Igny8_Logger::error("{$log_prefix} ❌ Cannot send status webhook - no API base URL", 'webhooks');
return;
}
$webhook_url = rtrim($api_base, '/') . '/integration/webhooks/wordpress/status/';
Igny8_Logger::info("{$log_prefix} Webhook URL: {$webhook_url}", 'webhooks');
// Get API key
$api_key = function_exists('igny8_get_secure_option') ? igny8_get_secure_option('igny8_api_key') : get_option('igny8_api_key');
if (!$api_key) {
Igny8_Logger::error("{$log_prefix} ❌ Cannot send status webhook - no API key", 'webhooks');
return;
}
Igny8_Logger::info("{$log_prefix} API Key: ***" . substr($api_key, -4), 'webhooks');
// Prepare webhook payload
$payload = array(
'post_id' => $post_id,
'content_id' => intval($content_id),
'post_status' => $post_status,
'post_url' => get_permalink($post_id),
'post_title' => get_the_title($post_id),
'site_url' => get_site_url(),
);
Igny8_Logger::info("{$log_prefix} Payload: " . json_encode($payload), 'webhooks');
// Send webhook asynchronously
Igny8_Logger::info("{$log_prefix} Sending POST request to IGNY8...", 'webhooks');
$response = wp_remote_post($webhook_url, array(
'headers' => array(
'Content-Type' => 'application/json',
'X-IGNY8-API-KEY' => $api_key,
),
'body' => json_encode($payload),
'timeout' => 10,
'blocking' => false, // Non-blocking for better performance
));
if (is_wp_error($response)) {
Igny8_Logger::error("{$log_prefix} ❌ Status webhook failed: " . $response->get_error_message(), 'webhooks');
} else {
Igny8_Logger::info("{$log_prefix} ✅ Status webhook sent successfully for content {$content_id}, post {$post_id}, status {$post_status}", 'webhooks');
}
Igny8_Logger::separator("{$log_prefix} ✅ WEBHOOK SEND COMPLETED");
}