Files
igny8/plugins/wordpress/source/igny8-wp-bridge/admin/class-admin.php
2026-01-13 09:23:54 +00:00

732 lines
24 KiB
PHP

<?php
/**
* Admin Interface Class
*
* Handles all admin functionality
*
* @package Igny8Bridge
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Igny8Admin Class
*/
class Igny8Admin {
/**
* Single instance of the class
*
* @var Igny8Admin
*/
private static $instance = null;
/**
* Get single instance
*
* @return Igny8Admin
*/
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*/
private function __construct() {
add_action('admin_menu', array($this, 'add_menu_pages'));
add_action('admin_init', array($this, 'register_settings'));
add_action('admin_enqueue_scripts', array($this, 'enqueue_scripts'));
}
/**
* Add admin menu pages
*/
public function add_menu_pages() {
// Add main menu page with dashicons (most reliable method)
add_menu_page(
__('IGNY8', 'igny8-bridge'),
__('IGNY8', 'igny8-bridge'),
'manage_options',
'igny8-connection',
array($this, 'render_page'),
'dashicons-cloud-saved',
58
);
// Add submenu pages - Dashboard is the main page (connection.php)
add_submenu_page(
'igny8-connection',
__('Dashboard', 'igny8-bridge'),
__('Dashboard', 'igny8-bridge'),
'manage_options',
'igny8-connection',
array($this, 'render_page')
);
add_submenu_page(
'igny8-connection',
__('Settings', 'igny8-bridge'),
__('Settings', 'igny8-bridge'),
'manage_options',
'igny8-settings',
array($this, 'render_page')
);
add_submenu_page(
'igny8-connection',
__('Logs', 'igny8-bridge'),
__('Logs', 'igny8-bridge'),
'manage_options',
'igny8-logs',
array($this, 'render_page')
);
}
/**
* Register settings
*/
public function register_settings() {
// Email/password settings removed - using API key only
register_setting('igny8_settings', 'igny8_site_id');
register_setting('igny8_bridge_connection', 'igny8_connection_enabled', array(
'type' => 'boolean',
'sanitize_callback' => array($this, 'sanitize_boolean'),
'default' => 1
));
register_setting('igny8_bridge_connection', 'igny8_connection_enabled', array(
'type' => 'boolean',
'sanitize_callback' => array($this, 'sanitize_boolean'),
'default' => 1
));
register_setting('igny8_bridge_controls', 'igny8_enabled_post_types', array(
'type' => 'array',
'sanitize_callback' => array($this, 'sanitize_post_types'),
'default' => array_keys(igny8_get_supported_post_types())
));
register_setting('igny8_bridge_controls', 'igny8_enabled_taxonomies', array(
'type' => 'array',
'sanitize_callback' => array($this, 'sanitize_taxonomies'),
'default' => array('category', 'post_tag', 'product_cat', 'igny8_sectors', 'igny8_clusters')
));
register_setting('igny8_bridge_controls', 'igny8_enable_woocommerce', array(
'type' => 'boolean',
'sanitize_callback' => array($this, 'sanitize_boolean'),
'default' => class_exists('WooCommerce') ? 1 : 0
));
register_setting('igny8_bridge_controls', 'igny8_control_mode', array(
'type' => 'string',
'sanitize_callback' => array($this, 'sanitize_control_mode'),
'default' => 'mirror'
));
register_setting('igny8_bridge_controls', 'igny8_enabled_modules', array(
'type' => 'array',
'sanitize_callback' => array($this, 'sanitize_modules'),
'default' => array_keys(igny8_get_available_modules())
));
// Default post status for IGNY8 published content
register_setting('igny8_bridge_controls', 'igny8_default_post_status', array(
'type' => 'string',
'sanitize_callback' => array($this, 'sanitize_post_status'),
'default' => 'draft'
));
// Sync enabled toggle
register_setting('igny8_bridge_controls', 'igny8_sync_enabled', array(
'type' => 'boolean',
'sanitize_callback' => 'absint',
'default' => 1
));
}
/**
* Enqueue admin scripts and styles
*
* @param string $hook Current admin page hook
*/
public function enqueue_scripts($hook) {
// Enqueue on IGNY8 pages
if (strpos($hook, 'igny8-') !== false || strpos($hook, 'toplevel_page_igny8') !== false) {
// Load modern design system CSS
wp_enqueue_style(
'igny8-modern-style',
IGNY8_BRIDGE_PLUGIN_URL . 'admin/assets/css/igny8-modern.css',
array(),
IGNY8_BRIDGE_VERSION
);
// Load legacy admin CSS for backwards compatibility
wp_enqueue_style(
'igny8-admin-style',
IGNY8_BRIDGE_PLUGIN_URL . 'admin/assets/css/admin.css',
array(),
IGNY8_BRIDGE_VERSION
);
wp_enqueue_script(
'igny8-admin-script',
IGNY8_BRIDGE_PLUGIN_URL . 'admin/assets/js/admin.js',
array('jquery'),
IGNY8_BRIDGE_VERSION,
true
);
wp_localize_script('igny8-admin-script', 'igny8Admin', array(
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('igny8_admin_nonce'),
));
}
// Enqueue on post/page/product list pages
if (strpos($hook, 'edit.php') !== false) {
$screen = get_current_screen();
if ($screen && in_array($screen->post_type, array('post', 'page', 'product', ''))) {
wp_enqueue_style(
'igny8-admin-style',
IGNY8_BRIDGE_PLUGIN_URL . 'admin/assets/css/admin.css',
array(),
IGNY8_BRIDGE_VERSION
);
wp_enqueue_script(
'igny8-admin-script',
IGNY8_BRIDGE_PLUGIN_URL . 'admin/assets/js/admin.js',
array('jquery'),
IGNY8_BRIDGE_VERSION,
true
);
wp_localize_script('igny8-admin-script', 'igny8Admin', array(
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('igny8_admin_nonce'),
));
}
}
}
/**
* Render page based on current menu slug
*/
public function render_page() {
// Handle form submissions for connection page
if (isset($_POST['igny8_connect'])) {
if (empty($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'igny8_settings_nonce')) {
add_settings_error(
'igny8_settings',
'igny8_nonce',
__('Security check failed. Please refresh the page and try again.', 'igny8-bridge'),
'error'
);
} else {
$this->handle_connection();
}
}
// Handle revoke API key
if (isset($_POST['igny8_revoke_api_key'])) {
if (empty($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'igny8_revoke_api_key')) {
add_settings_error(
'igny8_settings',
'igny8_nonce_revoke',
__('Security check failed. Could not revoke API key.', 'igny8-bridge'),
'error'
);
} else {
self::revoke_api_key();
add_settings_error(
'igny8_settings',
'igny8_api_key_revoked',
__('API key revoked and removed from this site.', 'igny8-bridge'),
'updated'
);
}
}
// Determine which page to render
$page = isset($_GET['page']) ? sanitize_text_field($_GET['page']) : 'igny8-connection';
// Add wrapper class for modern design
echo '<div class="igny8-admin-page">';
// Include the appropriate page template
$template_file = '';
switch ($page) {
case 'igny8-dashboard':
case 'igny8-connection':
// Dashboard is now the connection page
$template_file = IGNY8_BRIDGE_PLUGIN_DIR . 'admin/pages/connection.php';
break;
case 'igny8-settings':
$template_file = IGNY8_BRIDGE_PLUGIN_DIR . 'admin/pages/settings.php';
break;
case 'igny8-logs':
$template_file = IGNY8_BRIDGE_PLUGIN_DIR . 'admin/pages/logs.php';
break;
default:
$template_file = IGNY8_BRIDGE_PLUGIN_DIR . 'admin/pages/connection.php';
}
// If the template file doesn't exist, fall back to the old settings page
if (file_exists($template_file)) {
include $template_file;
} else {
// Fallback to old settings page during transition
include IGNY8_BRIDGE_PLUGIN_DIR . 'admin/settings.php';
}
echo '</div>';
}
/**
* Render settings page - DEPRECATED, keeping for backwards compatibility
*/
public function render_settings_page() {
// Redirect to new render_page method
$this->render_page();
}
/**
* Handle API connection - API key only
* Calls /v1/integration/integrations/test-connection/ endpoint
*/
private function handle_connection() {
$api_key = sanitize_text_field($_POST['igny8_api_key'] ?? '');
// API key is required
if (empty($api_key)) {
add_settings_error(
'igny8_settings',
'igny8_error',
__('API key is required to connect to IGNY8.', 'igny8-bridge'),
'error'
);
return;
}
// Extract site_id from API key format: igny8_site_{site_id}_{timestamp}_{random}
$site_id = null;
if (preg_match('/^igny8_site_(\d+)_/', $api_key, $matches)) {
$site_id = (int) $matches[1];
}
if (empty($site_id)) {
add_settings_error(
'igny8_settings',
'igny8_error',
__('Invalid API key format. Please copy the complete API key from IGNY8 app.', 'igny8-bridge'),
'error'
);
return;
}
// Get site URL
$site_url = get_site_url();
// Test connection using the correct integration test endpoint
// The API class will handle authentication for test-connection endpoint
// by using the API key from the request body
$api = new Igny8API();
$test_response = $api->post('/v1/integration/integrations/test-connection/', array(
'site_id' => $site_id,
'api_key' => $api_key,
'site_url' => $site_url
));
if (!$test_response['success']) {
$error_message = $test_response['error'] ?? 'Unknown error';
// Provide more user-friendly message for throttling errors
if (isset($test_response['http_status']) && $test_response['http_status'] === 429) {
$error_message = __('Rate limit exceeded. The plugin will automatically retry, but if this persists, please wait a moment and try again.', 'igny8-bridge');
}
add_settings_error(
'igny8_settings',
'igny8_error',
sprintf(
__('Failed to connect to IGNY8 API: %s', 'igny8-bridge'),
$error_message
),
'error'
);
return;
}
// Store API key securely
if (function_exists('igny8_store_secure_option')) {
igny8_store_secure_option('igny8_api_key', $api_key);
} else {
update_option('igny8_api_key', $api_key);
}
// Store site ID and last communication timestamp
update_option('igny8_site_id', sanitize_text_field($site_id));
update_option('igny8_last_communication', current_time('timestamp'));
add_settings_error(
'igny8_settings',
'igny8_connected',
__('Successfully connected to IGNY8 API. Site registered.', 'igny8-bridge'),
'updated'
);
// Sync site structure to backend (post types, taxonomies, etc.)
// Pass integration_id if available to avoid querying
igny8_sync_site_structure_to_backend($integration_id);
}
/**
* Revoke stored API key (secure delete)
*
* Public so tests can call it directly.
*/
public static function revoke_api_key() {
if (function_exists('igny8_delete_secure_option')) {
igny8_delete_secure_option('igny8_api_key');
} else {
delete_option('igny8_api_key');
}
// Clear connection data
delete_option('igny8_site_id');
delete_option('igny8_last_communication');
// Clean up deprecated options (for older installations)
delete_option('igny8_access_token');
delete_option('igny8_access_token_issued');
delete_option('igny8_integration_id');
delete_option('igny8_last_structure_sync');
delete_option('igny8_refresh_token');
delete_option('igny8_token_refreshed_at');
}
/**
* Test API connection (AJAX handler)
*/
public static function test_connection() {
check_ajax_referer('igny8_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => 'Unauthorized'));
}
if (!igny8_is_connection_enabled()) {
wp_send_json_error(array('message' => 'Connection is disabled. Enable sync operations to test.'));
}
$api = new Igny8API();
if (!$api->is_authenticated()) {
wp_send_json_error(array('message' => 'Not authenticated'));
}
// Get site ID
$site_id = get_option('igny8_site_id');
if (empty($site_id)) {
wp_send_json_error(array('message' => 'Site ID not configured. Connect to IGNY8 first.'));
}
// Test connection using the integration test endpoint
$api_key = function_exists('igny8_get_secure_option') ? igny8_get_secure_option('igny8_api_key') : get_option('igny8_api_key');
$test_response = $api->post('/v1/integration/integrations/test-connection/', array(
'site_id' => (int) $site_id,
'api_key' => $api_key,
'site_url' => get_site_url()
));
if ($test_response['success']) {
$checked_at = current_time('timestamp');
update_option('igny8_last_api_health_check', $checked_at);
wp_send_json_success(array(
'message' => __('Connection successful! IGNY8 API is responsive.', 'igny8-bridge'),
'checked_at' => $checked_at
));
return;
}
// Connection failed
$error_message = $test_response['error'] ?? 'Unknown error';
wp_send_json_error(array(
'message' => __('Connection failed: ', 'igny8-bridge') . $error_message,
'http_status' => $test_response['http_status'] ?? 0,
));
}
/**
* Sync posts to IGNY8 (AJAX handler)
*/
public static function sync_posts() {
check_ajax_referer('igny8_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => 'Unauthorized'));
}
if (!igny8_is_connection_enabled()) {
wp_send_json_error(array('message' => 'Connection is disabled. Enable sync operations first.'));
}
$result = igny8_batch_sync_post_statuses();
wp_send_json_success(array(
'message' => sprintf('Synced %d posts, %d failed', $result['synced'], $result['failed']),
'data' => $result
));
}
/**
* Sync taxonomies (AJAX handler)
*/
public static function sync_taxonomies() {
check_ajax_referer('igny8_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => 'Unauthorized'));
}
if (!igny8_is_connection_enabled()) {
wp_send_json_error(array('message' => 'Connection is disabled. Enable sync operations first.'));
}
$api = new Igny8API();
if (!$api->is_authenticated()) {
wp_send_json_error(array('message' => 'Not authenticated'));
}
// Sync sectors and clusters from IGNY8
$sectors_result = igny8_sync_igny8_sectors_to_wp();
$clusters_result = igny8_sync_igny8_clusters_to_wp();
wp_send_json_success(array(
'message' => sprintf('Synced %d sectors, %d clusters',
$sectors_result['synced'] ?? 0,
$clusters_result['synced'] ?? 0
),
'data' => array(
'sectors' => $sectors_result,
'clusters' => $clusters_result
)
));
}
/**
* Sync from IGNY8 (AJAX handler)
*/
public static function sync_from_igny8() {
check_ajax_referer('igny8_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => 'Unauthorized'));
}
if (!igny8_is_connection_enabled()) {
wp_send_json_error(array('message' => 'Connection is disabled. Enable sync operations first.'));
}
$result = igny8_sync_igny8_tasks_to_wp();
if ($result['success']) {
wp_send_json_success(array(
'message' => sprintf('Created %d posts, updated %d posts',
$result['created'],
$result['updated']
),
'data' => $result
));
} else {
wp_send_json_error(array(
'message' => $result['error'] ?? 'Sync failed'
));
}
}
/**
* Collect and send site data (AJAX handler)
*/
public static function collect_site_data() {
check_ajax_referer('igny8_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => 'Unauthorized'));
}
if (!igny8_is_connection_enabled()) {
wp_send_json_error(array('message' => 'Connection is disabled. Enable sync operations first.'));
}
$site_id = get_option('igny8_site_id');
if (!$site_id) {
wp_send_json_error(array('message' => 'Site ID not set'));
}
$result = igny8_send_site_data_to_igny8($site_id);
if ($result) {
wp_send_json_success(array(
'message' => 'Site data collected and sent successfully',
'data' => $result
));
} else {
wp_send_json_error(array('message' => 'Failed to send site data'));
}
}
/**
* Get sync statistics (AJAX handler)
*/
public static function get_stats() {
check_ajax_referer('igny8_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => 'Unauthorized'));
}
global $wpdb;
// Count synced posts
$synced_posts = $wpdb->get_var("
SELECT COUNT(DISTINCT post_id)
FROM {$wpdb->postmeta}
WHERE meta_key = '_igny8_task_id'
");
// Get last sync time
$last_sync = get_option('igny8_last_site_sync', 0);
$last_sync_formatted = $last_sync ? date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $last_sync) : 'Never';
wp_send_json_success(array(
'synced_posts' => intval($synced_posts),
'last_sync' => $last_sync_formatted
));
}
/**
* Sanitize post types option
*
* @param mixed $value Raw value
* @return array
*/
public function sanitize_post_types($value) {
$supported = array_keys(igny8_get_supported_post_types());
if (!is_array($value)) {
return $supported;
}
$clean = array();
foreach ($value as $post_type) {
$post_type = sanitize_key($post_type);
if (in_array($post_type, $supported, true)) {
$clean[] = $post_type;
}
}
return !empty($clean) ? $clean : $supported;
}
/**
* Sanitize taxonomies option
*
* @param mixed $value Raw value
* @return array
*/
public function sanitize_taxonomies($value) {
$supported = array_keys(igny8_get_supported_taxonomies());
if (!is_array($value)) {
return array('category', 'post_tag', 'product_cat', 'igny8_sectors', 'igny8_clusters');
}
$clean = array();
foreach ($value as $taxonomy) {
$taxonomy = sanitize_key($taxonomy);
if (in_array($taxonomy, $supported, true)) {
$clean[] = $taxonomy;
}
}
// Return defaults if nothing selected
return !empty($clean) ? $clean : array('category', 'post_tag');
}
/**
* Sanitize boolean option
*
* @param mixed $value Raw value
* @return int
*/
public function sanitize_boolean($value) {
return $value ? 1 : 0;
}
/**
* Sanitize control mode
*
* @param mixed $value Raw value
* @return string
*/
public function sanitize_control_mode($value) {
$value = is_string($value) ? strtolower($value) : 'mirror';
return in_array($value, array('mirror', 'hybrid'), true) ? $value : 'mirror';
}
/**
* Sanitize module toggles
*
* @param mixed $value Raw value
* @return array
*/
public function sanitize_modules($value) {
$supported = array_keys(igny8_get_available_modules());
if (!is_array($value)) {
return $supported;
}
$clean = array();
foreach ($value as $module) {
$module = sanitize_key($module);
if (in_array($module, $supported, true)) {
$clean[] = $module;
}
}
return !empty($clean) ? $clean : $supported;
}
/**
* Sanitize post status
*
* @param string $value Post status value
* @return string Sanitized post status
*/
public function sanitize_post_status($value) {
$allowed = array('draft', 'publish');
return in_array($value, $allowed, true) ? $value : 'draft';
}
}
// Register AJAX handlers
add_action('wp_ajax_igny8_test_connection', array('Igny8Admin', 'test_connection'));
add_action('wp_ajax_igny8_sync_posts', array('Igny8Admin', 'sync_posts'));
add_action('wp_ajax_igny8_sync_taxonomies', array('Igny8Admin', 'sync_taxonomies'));
add_action('wp_ajax_igny8_sync_from_igny8', array('Igny8Admin', 'sync_from_igny8'));
add_action('wp_ajax_igny8_collect_site_data', array('Igny8Admin', 'collect_site_data'));
add_action('wp_ajax_igny8_get_stats', array('Igny8Admin', 'get_stats'));