This commit is contained in:
alorig
2025-12-01 09:32:06 +05:00
parent aeaac01990
commit 71a38435b1
46 changed files with 75 additions and 17130 deletions

View File

@@ -1,882 +0,0 @@
<?php
/**
* Helper Functions
*
* WordPress integration functions for IGNY8 Bridge
*
* @package Igny8Bridge
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Get encryption key for secure option storage
*
* @return string Binary key
*/
function igny8_get_encryption_key() {
$salt = wp_salt('auth');
return hash('sha256', 'igny8_bridge_' . $salt, true);
}
/**
* Encrypt a value for storage
*
* @param string $value Plain text value
* @return string Encrypted value with prefix or original value on failure
*/
function igny8_encrypt_value($value) {
if ($value === '' || $value === null) {
return '';
}
if (!function_exists('openssl_encrypt')) {
return $value;
}
$iv = openssl_random_pseudo_bytes(16);
$cipher = openssl_encrypt($value, 'AES-256-CBC', igny8_get_encryption_key(), OPENSSL_RAW_DATA, $iv);
if ($cipher === false) {
return $value;
}
return 'igny8|' . base64_encode($iv . $cipher);
}
/**
* Decrypt a stored value
*
* @param string $value Stored value
* @return string Decrypted value or original on failure
*/
function igny8_decrypt_value($value) {
if (!is_string($value) || strpos($value, 'igny8|') !== 0) {
return $value;
}
if (!function_exists('openssl_decrypt')) {
return $value;
}
$encoded = substr($value, 6);
$data = base64_decode($encoded, true);
if ($data === false || strlen($data) <= 16) {
return $value;
}
$iv = substr($data, 0, 16);
$cipher = substr($data, 16);
$plain = openssl_decrypt($cipher, 'AES-256-CBC', igny8_get_encryption_key(), OPENSSL_RAW_DATA, $iv);
return ($plain === false) ? $value : $plain;
}
/**
* Store an option securely
*
* @param string $option Option name
* @param string $value Value to store
*/
function igny8_store_secure_option($option, $value) {
if ($value === null || $value === '') {
delete_option($option);
return;
}
update_option($option, igny8_encrypt_value($value));
}
/**
* Retrieve secure option (with legacy fallback)
*
* @param string $option Option name
* @return string Value
*/
function igny8_get_secure_option($option) {
$stored = get_option($option);
if (!$stored) {
return '';
}
$value = igny8_decrypt_value($stored);
return is_string($value) ? $value : '';
}
/**
* Get supported post types for automation
*
* @return array Key => label
*/
function igny8_get_supported_post_types() {
$types = array(
'post' => __('Posts', 'igny8-bridge'),
'page' => __('Pages', 'igny8-bridge'),
);
if (post_type_exists('product')) {
$types['product'] = __('Products', 'igny8-bridge');
}
/**
* Filter the list of selectable post types.
*
* @param array $types
*/
return apply_filters('igny8_supported_post_types', $types);
}
/**
* Get enabled post types
*
* @return array
*/
function igny8_get_enabled_post_types() {
$saved = get_option('igny8_enabled_post_types');
if (is_array($saved) && !empty($saved)) {
return $saved;
}
return array_keys(igny8_get_supported_post_types());
}
/**
* Get configured control mode
*
* @return string mirror|hybrid
*/
function igny8_get_control_mode() {
$mode = get_option('igny8_control_mode', 'mirror');
return in_array($mode, array('mirror', 'hybrid'), true) ? $mode : 'mirror';
}
/**
* Get supported taxonomies for syncing
*
* @return array Key => label
*/
function igny8_get_supported_taxonomies() {
$taxonomies = array();
// Standard WordPress taxonomies
if (taxonomy_exists('category')) {
$taxonomies['category'] = __('Categories', 'igny8-bridge');
}
if (taxonomy_exists('post_tag')) {
$taxonomies['post_tag'] = __('Tags', 'igny8-bridge');
}
// WooCommerce taxonomies
if (taxonomy_exists('product_cat')) {
$taxonomies['product_cat'] = __('Product Categories', 'igny8-bridge');
}
if (taxonomy_exists('product_tag')) {
$taxonomies['product_tag'] = __('Product Tags', 'igny8-bridge');
}
if (taxonomy_exists('product_shipping_class')) {
$taxonomies['product_shipping_class'] = __('Product Shipping Classes', 'igny8-bridge');
}
// IGNY8 taxonomies (always include)
if (taxonomy_exists('igny8_sectors')) {
$taxonomies['igny8_sectors'] = __('IGNY8 Sectors', 'igny8-bridge');
}
if (taxonomy_exists('igny8_clusters')) {
$taxonomies['igny8_clusters'] = __('IGNY8 Clusters', 'igny8-bridge');
}
// Get custom taxonomies (public only)
$custom_taxonomies = get_taxonomies(array(
'public' => true,
'_builtin' => false
), 'objects');
foreach ($custom_taxonomies as $taxonomy) {
// Skip if already added above
if (isset($taxonomies[$taxonomy->name])) {
continue;
}
// Skip post formats and other system taxonomies
if (in_array($taxonomy->name, array('post_format', 'wp_theme', 'wp_template_part_area'), true)) {
continue;
}
$taxonomies[$taxonomy->name] = $taxonomy->label;
}
/**
* Filter the list of selectable taxonomies.
*
* @param array $taxonomies
*/
return apply_filters('igny8_supported_taxonomies', $taxonomies);
}
/**
* Get enabled taxonomies for syncing
*
* @return array
*/
function igny8_get_enabled_taxonomies() {
$saved = get_option('igny8_enabled_taxonomies');
if (is_array($saved) && !empty($saved)) {
return $saved;
}
// Default: enable common taxonomies
return array('category', 'post_tag', 'product_cat', 'igny8_sectors', 'igny8_clusters');
}
/**
* Check if a taxonomy is enabled for syncing
*
* @param string $taxonomy Taxonomy key
* @return bool
*/
function igny8_is_taxonomy_enabled($taxonomy) {
$taxonomies = igny8_get_enabled_taxonomies();
return in_array($taxonomy, $taxonomies, true);
}
/**
* Get available automation modules
*
* @return array Key => label
*/
function igny8_get_available_modules() {
$modules = array(
'sites' => __('Sites (Data & Semantic Map)', 'igny8-bridge'),
'planner' => __('Planner (Keywords & Briefs)', 'igny8-bridge'),
'writer' => __('Writer (Tasks & Posts)', 'igny8-bridge'),
'linker' => __('Linker (Internal Links)', 'igny8-bridge'),
'optimizer' => __('Optimizer (Audits & Scores)', 'igny8-bridge'),
);
/**
* Filter the list of IGNY8 modules that can be toggled.
*
* @param array $modules
*/
return apply_filters('igny8_available_modules', $modules);
}
/**
* Get enabled modules
*
* @return array
*/
function igny8_get_enabled_modules() {
$saved = get_option('igny8_enabled_modules');
if (is_array($saved) && !empty($saved)) {
return $saved;
}
return array_keys(igny8_get_available_modules());
}
/**
* Check if a module is enabled
*
* @param string $module Module key
* @return bool
*/
function igny8_is_module_enabled($module) {
$modules = igny8_get_enabled_modules();
return in_array($module, $modules, true);
}
/**
* Check if a post type is enabled for automation
*
* @param string $post_type Post type key
* @return bool
*/
function igny8_is_post_type_enabled($post_type) {
$post_types = igny8_get_enabled_post_types();
return in_array($post_type, $post_types, true);
}
/**
* Check if IGNY8 connection is enabled
* This is a master switch that disables all sync operations while preserving credentials
*
* @return bool True if connection is enabled
*/
if (!function_exists('igny8_log_error')) {
function igny8_log_error($message) {
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('[IGNY8 Plugin] ' . $message);
}
}
}
if (!function_exists('igny8_is_connection_enabled')) {
function igny8_is_connection_enabled() {
// Master toggle (defaults to true)
$enabled = (bool) get_option('igny8_connection_enabled', 1);
if (!$enabled) {
return false;
}
// Prefer secure option helpers when available
if (function_exists('igny8_get_secure_option')) {
$api_key = igny8_get_secure_option('igny8_api_key');
} else {
$api_key = get_option('igny8_api_key');
}
$site_id = get_option('igny8_site_id');
if (empty($api_key) || empty($site_id)) {
igny8_log_error('Failed to connect to IGNY8 API: API key or Site ID not configured.');
return false;
}
return true;
}
}
/**
* Get connection state
* Three states: not_connected, configured, connected
*
* @return string Connection state
*/
function igny8_get_connection_state() {
$api_key = function_exists('igny8_get_secure_option') ? igny8_get_secure_option('igny8_api_key') : get_option('igny8_api_key');
$integration_id = get_option('igny8_integration_id');
$last_structure_sync = get_option('igny8_last_structure_sync');
if (empty($api_key)) {
igny8_log_connection_state('not_connected', 'No API key found');
return 'not_connected';
}
if (!empty($api_key) && !empty($integration_id) && !empty($last_structure_sync)) {
igny8_log_connection_state('connected', 'Fully connected and synced');
return 'connected';
}
igny8_log_connection_state('configured', 'API key set, pending structure sync');
return 'configured';
}
/**
* Log connection state changes (without exposing API keys)
*
* @param string $state Connection state
* @param string $message Additional context
*/
function igny8_log_connection_state($state, $message = '') {
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log(sprintf('[IGNY8 Connection] State: %s | %s', $state, $message));
}
}
/**
* Log sync operations (without exposing sensitive data)
*
* @param string $operation Operation name
* @param array $context Context data
*/
function igny8_log_sync($operation, $context = array()) {
if (defined('WP_DEBUG') && WP_DEBUG) {
// Filter out sensitive keys
$safe_context = array_diff_key($context, array_flip(['api_key', 'password', 'secret', 'token']));
error_log(sprintf('[IGNY8 Sync] %s | Context: %s', $operation, json_encode($safe_context)));
}
}
/**
* Get configuration for site scans
*
* @param array $overrides Override defaults
* @return array
*/
function igny8_get_site_scan_settings($overrides = array()) {
$defaults = array(
'post_types' => igny8_get_enabled_post_types(),
'include_products' => (bool) get_option('igny8_enable_woocommerce', class_exists('WooCommerce') ? 1 : 0),
'per_page' => 100,
'since' => null,
'mode' => 'full',
);
$settings = wp_parse_args($overrides, $defaults);
return apply_filters('igny8_site_scan_settings', $settings);
}
/**
* Register IGNY8 post meta fields
*/
function igny8_register_post_meta() {
$post_types = array('post', 'page', 'product');
// Define all meta fields with proper schema for REST API
$meta_fields = array(
'_igny8_taxonomy_id' => array(
'type' => 'integer',
'description' => 'IGNY8 taxonomy ID linked to this post',
'single' => true,
'show_in_rest' => true,
),
'_igny8_attribute_id' => array(
'type' => 'integer',
'description' => 'IGNY8 attribute ID linked to this post',
'single' => true,
'show_in_rest' => true,
),
'_igny8_last_synced' => array(
'type' => 'string',
'description' => 'Last sync timestamp',
'single' => true,
'show_in_rest' => true,
)
);
// Register each meta field for all relevant post types
foreach ($meta_fields as $meta_key => $config) {
foreach ($post_types as $post_type) {
register_post_meta($post_type, $meta_key, $config);
}
}
}
/**
* Register IGNY8 taxonomies
*/
function igny8_register_taxonomies() {
// Register sectors taxonomy (hierarchical) - only if not exists
if (!taxonomy_exists('igny8_sectors')) {
register_taxonomy('igny8_sectors', array('post', 'page', 'product'), array(
'hierarchical' => true,
'labels' => array(
'name' => 'IGNY8 Sectors',
'singular_name' => 'Sector',
'menu_name' => 'Sectors',
'all_items' => 'All Sectors',
'edit_item' => 'Edit Sector',
'view_item' => 'View Sector',
'update_item' => 'Update Sector',
'add_new_item' => 'Add New Sector',
'new_item_name' => 'New Sector Name',
'parent_item' => 'Parent Sector',
'parent_item_colon' => 'Parent Sector:',
'search_items' => 'Search Sectors',
'not_found' => 'No sectors found',
),
'public' => true,
'show_ui' => true,
'show_admin_column' => false,
'show_in_nav_menus' => true,
'show_tagcloud' => false,
'show_in_rest' => true,
'rewrite' => array(
'slug' => 'sectors',
'with_front' => false,
),
'capabilities' => array(
'manage_terms' => 'manage_categories',
'edit_terms' => 'manage_categories',
'delete_terms' => 'manage_categories',
'assign_terms' => 'edit_posts',
),
));
}
// Register clusters taxonomy (hierarchical) - only if not exists
if (!taxonomy_exists('igny8_clusters')) {
register_taxonomy('igny8_clusters', array('post', 'page', 'product'), array(
'hierarchical' => true,
'labels' => array(
'name' => 'IGNY8 Clusters',
'singular_name' => 'Cluster',
'menu_name' => 'Clusters',
'all_items' => 'All Clusters',
'edit_item' => 'Edit Cluster',
'view_item' => 'View Cluster',
'update_item' => 'Update Cluster',
'add_new_item' => 'Add New Cluster',
'new_item_name' => 'New Cluster Name',
'parent_item' => 'Parent Cluster',
'parent_item_colon' => 'Parent Cluster:',
'search_items' => 'Search Clusters',
'not_found' => 'No clusters found',
),
'public' => true,
'show_ui' => true,
'show_admin_column' => false,
'show_in_nav_menus' => true,
'show_tagcloud' => false,
'show_in_rest' => true,
'rewrite' => array(
'slug' => 'clusters',
'with_front' => false,
),
'capabilities' => array(
'manage_terms' => 'manage_categories',
'edit_terms' => 'manage_categories',
'delete_terms' => 'manage_categories',
'assign_terms' => 'edit_posts',
),
));
}
}
/**
* Map WordPress post status to IGNY8 task status
*
* @param string $wp_status WordPress post status
* @return string IGNY8 task status
*/
function igny8_map_wp_status_to_igny8($wp_status) {
$status_map = array(
'publish' => 'completed',
'draft' => 'draft',
'pending' => 'pending',
'private' => 'completed',
'trash' => 'archived',
'future' => 'scheduled'
);
return isset($status_map[$wp_status]) ? $status_map[$wp_status] : 'draft';
}
/**
* Check if post is managed by IGNY8
*
* @param int $post_id Post ID
* @return bool True if IGNY8 managed
*/
function igny8_is_igny8_managed_post($post_id) {
$task_id = get_post_meta($post_id, '_igny8_task_id', true);
return !empty($task_id);
}
/**
* Get post data for IGNY8 sync
*
* @param int $post_id Post ID
* @return array|false Post data or false on failure
*/
function igny8_get_post_data_for_sync($post_id) {
$post = get_post($post_id);
if (!$post) {
return false;
}
return array(
'id' => $post_id,
'title' => $post->post_title,
'status' => $post->post_status,
'url' => get_permalink($post_id),
'modified' => $post->post_modified,
'published' => $post->post_date,
'author' => get_the_author_meta('display_name', $post->post_author),
'word_count' => str_word_count(strip_tags($post->post_content)),
'meta' => array(
'task_id' => get_post_meta($post_id, '_igny8_task_id', true),
'content_id' => get_post_meta($post_id, '_igny8_content_id', true),
'cluster_id' => get_post_meta($post_id, '_igny8_cluster_id', true),
'sector_id' => get_post_meta($post_id, '_igny8_sector_id', true),
)
);
}
/**
* Schedule cron jobs
*/
function igny8_schedule_cron_jobs() {
// Schedule daily post status sync (WordPress → IGNY8)
if (!wp_next_scheduled('igny8_sync_post_statuses')) {
wp_schedule_event(time(), 'daily', 'igny8_sync_post_statuses');
}
// Schedule daily site data sync (incremental)
if (!wp_next_scheduled('igny8_sync_site_data')) {
wp_schedule_event(time(), 'daily', 'igny8_sync_site_data');
}
// Schedule periodic full site scan (runs at most once per week)
if (!wp_next_scheduled('igny8_full_site_scan')) {
wp_schedule_event(time(), 'daily', 'igny8_full_site_scan');
}
// Schedule hourly sync from IGNY8 (IGNY8 → WordPress)
if (!wp_next_scheduled('igny8_sync_from_igny8')) {
wp_schedule_event(time(), 'hourly', 'igny8_sync_from_igny8');
}
// Schedule taxonomy sync
if (!wp_next_scheduled('igny8_sync_taxonomies')) {
wp_schedule_event(time(), 'twicedaily', 'igny8_sync_taxonomies');
}
// Schedule keyword sync
if (!wp_next_scheduled('igny8_sync_keywords')) {
wp_schedule_event(time(), 'daily', 'igny8_sync_keywords');
}
// Schedule site structure sync (daily - to keep post types, taxonomies counts up to date)
if (!wp_next_scheduled('igny8_sync_site_structure')) {
wp_schedule_event(time(), 'daily', 'igny8_sync_site_structure');
}
}
/**
* Unschedule cron jobs
*/
function igny8_unschedule_cron_jobs() {
$timestamp = wp_next_scheduled('igny8_sync_post_statuses');
if ($timestamp) {
wp_unschedule_event($timestamp, 'igny8_sync_post_statuses');
}
$timestamp = wp_next_scheduled('igny8_sync_site_data');
if ($timestamp) {
wp_unschedule_event($timestamp, 'igny8_sync_site_data');
}
$timestamp = wp_next_scheduled('igny8_sync_from_igny8');
if ($timestamp) {
wp_unschedule_event($timestamp, 'igny8_sync_from_igny8');
}
$timestamp = wp_next_scheduled('igny8_full_site_scan');
if ($timestamp) {
wp_unschedule_event($timestamp, 'igny8_full_site_scan');
}
$timestamp = wp_next_scheduled('igny8_sync_taxonomies');
if ($timestamp) {
wp_unschedule_event($timestamp, 'igny8_sync_taxonomies');
}
$timestamp = wp_next_scheduled('igny8_sync_keywords');
if ($timestamp) {
wp_unschedule_event($timestamp, 'igny8_sync_keywords');
}
$timestamp = wp_next_scheduled('igny8_sync_site_structure');
if ($timestamp) {
wp_unschedule_event($timestamp, 'igny8_sync_site_structure');
}
}
/**
* Get WordPress site structure (post types and taxonomies with counts)
*
* @return array Site structure with post types and taxonomies
*/
function igny8_get_site_structure() {
$post_types_data = array();
$taxonomies_data = array();
// Get all registered post types
$post_types = get_post_types(array('public' => true), 'objects');
foreach ($post_types as $post_type) {
// Skip built-in post types we don't care about
if (in_array($post_type->name, array('attachment'), true)) {
continue;
}
$count = wp_count_posts($post_type->name);
$total = 0;
foreach ((array) $count as $status => $num) {
if ($status !== 'auto-draft') {
$total += (int) $num;
}
}
if ($total > 0 || in_array($post_type->name, array('post', 'page', 'product'), true)) {
$post_types_data[$post_type->name] = array(
'label' => $post_type->label ?: $post_type->name,
'count' => $total,
'enabled' => igny8_is_post_type_enabled($post_type->name),
'fetch_limit' => 100,
);
}
}
// Get all registered taxonomies
$taxonomies = get_taxonomies(array('public' => true), 'objects');
foreach ($taxonomies as $taxonomy) {
// Skip built-in taxonomies we don't care about
if (in_array($taxonomy->name, array('post_format'), true)) {
continue;
}
$terms = get_terms(array(
'taxonomy' => $taxonomy->name,
'hide_empty' => false,
'number' => 0,
));
$count = is_array($terms) ? count($terms) : 0;
if ($count > 0 || in_array($taxonomy->name, array('category', 'post_tag', 'product_cat'), true)) {
$taxonomies_data[$taxonomy->name] = array(
'label' => $taxonomy->label ?: $taxonomy->name,
'count' => $count,
'enabled' => true,
'fetch_limit' => 100,
);
}
}
return array(
'post_types' => $post_types_data,
'taxonomies' => $taxonomies_data,
'timestamp' => current_time('c'),
);
}
/* Duplicate function removed. See guarded implementation above. */
/**
* Sync WordPress site structure to IGNY8 backend
* Called after connection is established
*
* @return bool True on success, false on failure
*/
function igny8_sync_site_structure_to_backend($integration_id = null) {
// Get site ID from options
$site_id = get_option('igny8_site_id');
if (!$site_id) {
igny8_log_sync('structure_sync_failed', array('reason' => 'No site ID found'));
return false;
}
// Get the site structure
$structure = igny8_get_site_structure();
if (empty($structure['post_types']) && empty($structure['taxonomies'])) {
igny8_log_sync('structure_sync_skipped', array('reason' => 'No post types or taxonomies'));
return false;
}
// Create a temporary integration object to find the actual integration ID
$api = new Igny8API();
if (!$api->is_authenticated()) {
igny8_log_sync('structure_sync_failed', array('reason' => 'Not authenticated'));
return false;
}
// Use provided integration_id if available, otherwise query for it
$integration = null;
if ($integration_id) {
// Use the provided integration_id directly
$integration = array('id' => $integration_id);
igny8_log_sync('structure_sync_start', array('integration_id' => $integration_id, 'site_id' => $site_id));
} else {
// Fallback: Get integration_id from stored option
$stored_integration_id = get_option('igny8_integration_id');
if ($stored_integration_id) {
$integration = array('id' => intval($stored_integration_id));
igny8_log_sync('structure_sync_start', array('integration_id' => $stored_integration_id, 'site_id' => $site_id, 'source' => 'stored_option'));
} else {
// Last resort: Query for integrations
$response = $api->get('/v1/integration/integrations/?site=' . $site_id);
if (!$response['success'] || empty($response['data'])) {
igny8_log_sync('structure_sync_failed', array('reason' => 'No integrations found'));
return false;
}
// Get the first integration (should be WordPress integration)
if (isset($response['data']['results']) && !empty($response['data']['results'])) {
$integration = $response['data']['results'][0];
} elseif (is_array($response['data']) && !empty($response['data'])) {
$integration = $response['data'][0];
}
if ($integration && !empty($integration['id'])) {
// Store integration_id for future use
update_option('igny8_integration_id', intval($integration['id']));
igny8_log_sync('integration_id_saved', array('integration_id' => $integration['id']));
}
}
}
if (!$integration || empty($integration['id'])) {
igny8_log_sync('structure_sync_failed', array('reason' => 'Invalid integration'));
return false;
}
// Prepare the payload
$payload = array(
'post_types' => $structure['post_types'],
'taxonomies' => $structure['taxonomies'],
'timestamp' => $structure['timestamp'],
'plugin_connection_enabled' => (bool) igny8_is_connection_enabled(),
'two_way_sync_enabled' => (bool) get_option('igny8_enable_two_way_sync', 1),
);
igny8_log_sync('structure_sync_sending', array(
'post_types_count' => count($structure['post_types']),
'taxonomies_count' => count($structure['taxonomies'])
));
// Send to backend
$endpoint = '/v1/integration/integrations/' . $integration['id'] . '/update-structure/';
$update_response = $api->post($endpoint, $payload);
if ($update_response['success']) {
igny8_log_sync('structure_sync_success', array(
'post_types' => count($structure['post_types']),
'taxonomies' => count($structure['taxonomies'])
));
update_option('igny8_last_structure_sync', current_time('timestamp'));
return true;
} else {
igny8_log_sync('structure_sync_failed', array(
'error' => $update_response['error'] ?? 'Unknown error',
'http_status' => $update_response['http_status'] ?? 0
));
return false;
}
}
if (!function_exists('igny8_handle_rate_limit')) {
function igny8_handle_rate_limit($response, $max_retries = 3) {
if (isset($response['error']) && strpos($response['error'], 'Rate limit') !== false) {
for ($attempt = 0; $attempt < $max_retries; $attempt++) {
sleep(pow(2, $attempt)); // Exponential backoff
$response = igny8_retry_request(); // Retry logic (to be implemented)
if ($response['success']) {
return $response;
}
}
igny8_log_error('Max retries exceeded for rate-limited request.');
}
return $response;
}
}
if (!function_exists('igny8_retry_request')) {
function igny8_retry_request() {
// Placeholder for retry logic
return ['success' => false, 'error' => 'Retry logic not implemented'];
}
}