Files
igny8/igny8-wp-plugin/includes/class-igny8-rest-api.php
alorig e99bec5067 fix
2025-11-22 12:50:59 +05:00

488 lines
17 KiB
PHP

<?php
/**
* REST API Endpoints for IGNY8
*
* Provides endpoints for IGNY8 to query WordPress posts by content_id
*
* @package Igny8Bridge
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Igny8RestAPI Class
*/
class Igny8RestAPI {
/**
* Constructor
*/
public function __construct() {
add_action('rest_api_init', array($this, 'register_routes'));
}
/**
* Register REST API routes
*/
public function register_routes() {
// Get post by IGNY8 content_id
register_rest_route('igny8/v1', '/post-by-content-id/(?P<content_id>\d+)', array(
'methods' => 'GET',
'callback' => array($this, 'get_post_by_content_id'),
'permission_callback' => array($this, 'check_permission'),
'args' => array(
'content_id' => array(
'required' => true,
'type' => 'integer',
'description' => 'IGNY8 content ID'
)
)
));
// Get post by IGNY8 task_id
register_rest_route('igny8/v1', '/post-by-task-id/(?P<task_id>\d+)', array(
'methods' => 'GET',
'callback' => array($this, 'get_post_by_task_id'),
'permission_callback' => array($this, 'check_permission'),
'args' => array(
'task_id' => array(
'required' => true,
'type' => 'integer',
'description' => 'IGNY8 task ID'
)
)
));
// Get post status by content_id
register_rest_route('igny8/v1', '/post-status/(?P<content_id>\d+)', array(
'methods' => 'GET',
'callback' => array($this, 'get_post_status_by_content_id'),
'permission_callback' => array($this, 'check_permission'),
'args' => array(
'content_id' => array(
'required' => true,
'type' => 'integer',
'description' => 'IGNY8 content ID'
)
)
));
// Site metadata - post types, taxonomies and counts (unified response format)
register_rest_route('igny8/v1', '/site-metadata/', array(
'methods' => 'GET',
// We perform permission checks inside callback to ensure unified response format
'callback' => array($this, 'get_site_metadata'),
'permission_callback' => '__return_true',
));
// Status endpoint for health checks (public)
register_rest_route('igny8/v1', '/status', array(
'methods' => 'GET',
'callback' => array($this, 'get_plugin_status'),
'permission_callback' => '__return_true', // Public endpoint for health checks
));
}
/**
* Check API permission
*
* @param WP_REST_Request $request Request object
* @return bool|WP_Error
*/
public function check_permission($request) {
// Do NOT block endpoints when the plugin connection is disabled.
// The plugin-side "Enable Sync Operations" option should only stop background sync actions,
// but REST discovery endpoints should remain callable. Authentication is still required.
// Check if authenticated with IGNY8 via stored token OR X-IGNY8-API-KEY header
$api = new Igny8API();
// Accept explicit X-IGNY8-API-KEY header for incoming requests
$header_api_key = $request->get_header('x-igny8-api-key');
if ($header_api_key) {
$stored_api_key = function_exists('igny8_get_secure_option') ? igny8_get_secure_option('igny8_api_key') : get_option('igny8_api_key');
if ($stored_api_key && hash_equals($stored_api_key, $header_api_key)) {
return true;
}
}
if (!$api->is_authenticated()) {
return new WP_Error(
'rest_forbidden',
__('IGNY8 API not authenticated', 'igny8-bridge'),
array('status' => 401)
);
}
// Verify API token from request header
$auth_header = $request->get_header('Authorization');
if ($auth_header) {
$token = get_option('igny8_access_token');
if ($token && strpos($auth_header, 'Bearer ' . $token) !== false) {
return true;
}
}
// Allow if IGNY8 is connected (for internal use)
if ($api->is_authenticated()) {
return true;
}
return new WP_Error(
'rest_forbidden',
__('Invalid authentication', 'igny8-bridge'),
array('status' => 403)
);
}
/**
* Get post by content_id
*
* @param WP_REST_Request $request Request object
* @return WP_REST_Response|WP_Error
*/
public function get_post_by_content_id($request) {
// Double-check connection is enabled
if (!igny8_is_connection_enabled()) {
return new WP_Error(
'rest_forbidden',
__('IGNY8 connection is disabled', 'igny8-bridge'),
array('status' => 403)
);
}
$content_id = intval($request['content_id']);
// Find post by content_id meta
$posts = get_posts(array(
'meta_key' => '_igny8_content_id',
'meta_value' => $content_id,
'post_type' => 'any',
'posts_per_page' => 1,
'post_status' => 'any'
));
if (empty($posts)) {
return new WP_Error(
'rest_not_found',
__('Post not found for this content ID', 'igny8-bridge'),
array('status' => 404)
);
}
$post = $posts[0];
return rest_ensure_response(array(
'success' => true,
'data' => array(
'post_id' => $post->ID,
'title' => $post->post_title,
'status' => $post->post_status,
'wordpress_status' => $post->post_status,
'igny8_status' => igny8_map_wp_status_to_igny8($post->post_status),
'url' => get_permalink($post->ID),
'post_type' => $post->post_type,
'content_id' => $content_id,
'task_id' => get_post_meta($post->ID, '_igny8_task_id', true),
'last_synced' => get_post_meta($post->ID, '_igny8_last_synced', true)
)
));
}
/**
* Get post by task_id
*
* @param WP_REST_Request $request Request object
* @return WP_REST_Response|WP_Error
*/
public function get_post_by_task_id($request) {
// Double-check connection is enabled
if (!igny8_is_connection_enabled()) {
return new WP_Error(
'rest_forbidden',
__('IGNY8 connection is disabled', 'igny8-bridge'),
array('status' => 403)
);
}
$task_id = intval($request['task_id']);
// Find post by task_id meta
$posts = get_posts(array(
'meta_key' => '_igny8_task_id',
'meta_value' => $task_id,
'post_type' => 'any',
'posts_per_page' => 1,
'post_status' => 'any'
));
if (empty($posts)) {
return new WP_Error(
'rest_not_found',
__('Post not found for this task ID', 'igny8-bridge'),
array('status' => 404)
);
}
$post = $posts[0];
return rest_ensure_response(array(
'success' => true,
'data' => array(
'post_id' => $post->ID,
'title' => $post->post_title,
'status' => $post->post_status,
'wordpress_status' => $post->post_status,
'igny8_status' => igny8_map_wp_status_to_igny8($post->post_status),
'url' => get_permalink($post->ID),
'post_type' => $post->post_type,
'task_id' => $task_id,
'content_id' => get_post_meta($post->ID, '_igny8_content_id', true),
'last_synced' => get_post_meta($post->ID, '_igny8_last_synced', true)
)
));
}
/**
* Get post status by content_id
*
* @param WP_REST_Request $request Request object
* @return WP_REST_Response|WP_Error
*/
public function get_post_status_by_content_id($request) {
// Double-check connection is enabled
if (!igny8_is_connection_enabled()) {
return new WP_Error(
'rest_forbidden',
__('IGNY8 connection is disabled', 'igny8-bridge'),
array('status' => 403)
);
}
$content_id = intval($request['content_id']);
// Find post by content_id meta
$posts = get_posts(array(
'meta_key' => '_igny8_content_id',
'meta_value' => $content_id,
'post_type' => 'any',
'posts_per_page' => 1,
'post_status' => 'any',
'fields' => 'ids' // Only get IDs for performance
));
if (empty($posts)) {
return rest_ensure_response(array(
'success' => false,
'message' => 'Post not found',
'content_id' => $content_id
));
}
$post_id = $posts[0];
$post = get_post($post_id);
return rest_ensure_response(array(
'success' => true,
'data' => array(
'post_id' => $post_id,
'wordpress_status' => $post->post_status,
'igny8_status' => igny8_map_wp_status_to_igny8($post->post_status),
'status_mapping' => array(
'publish' => 'completed',
'draft' => 'draft',
'pending' => 'pending',
'private' => 'completed',
'trash' => 'archived',
'future' => 'scheduled'
),
'content_id' => $content_id,
'url' => get_permalink($post_id),
'last_synced' => get_post_meta($post_id, '_igny8_last_synced', true)
)
));
}
/**
* Helper: generate a request_id (UUIDv4 if available)
*
* @return string
*/
private function generate_request_id() {
if (function_exists('wp_generate_uuid4')) {
return wp_generate_uuid4();
}
// Fallback: uniqid with more entropy
return uniqid('', true);
}
/**
* Helper: Build unified API response and return WP_REST_Response
*
* @param bool $success
* @param mixed $data
* @param string|null $message
* @param string|null $error
* @param array|null $errors
* @param int $status
* @return WP_REST_Response
*/
private function build_unified_response($success, $data = null, $message = null, $error = null, $errors = null, $status = 200) {
$payload = array(
'success' => (bool) $success,
'data' => $data,
'message' => $message,
'request_id' => $this->generate_request_id()
);
if (!$success) {
$payload['error'] = $error ?: 'Unknown error';
if (!empty($errors)) {
$payload['errors'] = $errors;
}
}
$response = rest_ensure_response($payload);
$response->set_status($status);
return $response;
}
/**
* GET /site-metadata/ - returns post types, taxonomies and counts in unified format
*
* @param WP_REST_Request $request
* @return WP_REST_Response
*/
public function get_site_metadata($request) {
// Use transient cache to avoid expensive counts on large sites
$cache_key = 'igny8_site_metadata_v1';
$cached = get_transient($cache_key);
if ($cached !== false) {
return $this->build_unified_response(true, $cached, 'Site metadata (cached)', null, null, 200);
}
// Perform permission check and return unified error if not allowed
$perm = $this->check_permission($request);
if (is_wp_error($perm)) {
$status = 403;
$error_data = $perm->get_error_data();
if (is_array($error_data) && isset($error_data['status'])) {
$status = intval($error_data['status']);
}
return $this->build_unified_response(false, null, null, $perm->get_error_message(), null, $status);
}
// Collect post types (public)
$post_types_objects = get_post_types(array('public' => true), 'objects');
$post_types = array();
foreach ($post_types_objects as $slug => $obj) {
// Get total count across statuses
$count_obj = wp_count_posts($slug);
$total = 0;
if (is_object($count_obj)) {
foreach (get_object_vars($count_obj) as $val) {
$total += intval($val);
}
}
$post_types[$slug] = array(
'label' => $obj->labels->singular_name ?? $obj->label,
'count' => $total
);
}
// Collect taxonomies (public)
$taxonomy_objects = get_taxonomies(array('public' => true), 'objects');
$taxonomies = array();
foreach ($taxonomy_objects as $slug => $obj) {
// Use wp_count_terms when available
$term_count = 0;
if (function_exists('wp_count_terms')) {
$term_count = intval(wp_count_terms($slug));
} else {
$terms = get_terms(array('taxonomy' => $slug, 'hide_empty' => false, 'fields' => 'ids'));
$term_count = is_array($terms) ? count($terms) : 0;
}
$taxonomies[$slug] = array(
'label' => $obj->labels->name ?? $obj->label,
'count' => $term_count
);
}
$data = array(
'post_types' => $post_types,
'taxonomies' => $taxonomies,
'generated_at' => time(),
'plugin_connection_enabled' => (bool) igny8_is_connection_enabled(),
'two_way_sync_enabled' => (bool) get_option('igny8_enable_two_way_sync', 1)
);
// Cache for 5 minutes
set_transient($cache_key, $data, 300);
return $this->build_unified_response(true, $data, 'Site metadata retrieved', null, null, 200);
}
/**
* Get plugin status for health checks
*
* @param WP_REST_Request $request Request object
* @return WP_REST_Response
*/
public function get_plugin_status($request) {
// Get plugin configuration status
$api_key = function_exists('igny8_get_secure_option') ? igny8_get_secure_option('igny8_api_key') : get_option('igny8_api_key');
$access_token = function_exists('igny8_get_secure_option') ? igny8_get_secure_option('igny8_access_token') : get_option('igny8_access_token');
$email = get_option('igny8_email', '');
$site_id = get_option('igny8_site_id', '');
$connection_enabled = igny8_is_connection_enabled();
$two_way_sync = (int) get_option('igny8_enable_two_way_sync', 1);
// Check if plugin is configured
$has_api_key = !empty($api_key);
$has_access_token = !empty($access_token);
$has_site_id = !empty($site_id);
$is_configured = $has_api_key && $has_access_token && $has_site_id && $connection_enabled;
// Get last sync times
$last_site_sync = intval(get_option('igny8_last_site_sync', 0));
$last_structure_sync = intval(get_option('igny8_last_structure_sync', 0));
// Determine plugin status
$status = 'not_configured';
if ($is_configured && ($last_site_sync > 0 || $last_structure_sync > 0)) {
$status = 'active';
} elseif ($is_configured) {
$status = 'configured';
} elseif ($has_api_key || $has_access_token) {
$status = 'partial';
}
// Build response
return rest_ensure_response(array(
'plugin' => 'IGNY8 WordPress Bridge',
'version' => defined('IGNY8_BRIDGE_VERSION') ? IGNY8_BRIDGE_VERSION : 'unknown',
'status' => $status,
'connected' => $is_configured,
'has_api_key' => $has_api_key,
'has_site_id' => $has_site_id,
'connection_enabled' => $connection_enabled,
'two_way_sync_enabled' => (bool) $two_way_sync,
'last_site_sync' => $last_site_sync > 0 ? gmdate('Y-m-d\TH:i:s\Z', $last_site_sync) : null,
'last_structure_sync' => $last_structure_sync > 0 ? gmdate('Y-m-d\TH:i:s\Z', $last_structure_sync) : null,
'can_reach_igny8' => $last_site_sync > 0 || $last_structure_sync > 0, // If we've synced, we can reach IGNY8
'timestamp' => current_time('mysql'),
'site_url' => get_site_url(),
'site_name' => get_bloginfo('name'),
));
}
}
// Initialize REST API
new Igny8RestAPI();