631 lines
22 KiB
PHP
631 lines
22 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 or post_id
|
|
register_rest_route('igny8/v1', '/post-status/(?P<id>\d+)', array(
|
|
'methods' => 'GET',
|
|
'callback' => array($this, 'get_post_status'),
|
|
'permission_callback' => array($this, 'check_permission'),
|
|
'args' => array(
|
|
'id' => array(
|
|
'required' => true,
|
|
'type' => 'integer',
|
|
'description' => 'WordPress post ID or IGNY8 content ID (tries both)'
|
|
)
|
|
)
|
|
));
|
|
|
|
// 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',
|
|
));
|
|
|
|
// Plugin status endpoint - returns connection status and API key info
|
|
register_rest_route('igny8/v1', '/status', array(
|
|
'methods' => 'GET',
|
|
'callback' => array($this, 'get_status'),
|
|
'permission_callback' => '__return_true', // Public endpoint for health checks
|
|
));
|
|
|
|
// Manual publish endpoint - for triggering WordPress publish from IGNY8
|
|
// Route: /wp-json/igny8/v1/publish
|
|
register_rest_route('igny8/v1', '/publish', array(
|
|
'methods' => 'POST',
|
|
'callback' => array($this, 'publish_content_to_wordpress'),
|
|
'permission_callback' => array($this, 'check_permission'),
|
|
'args' => array(
|
|
'content_id' => array(
|
|
'required' => true,
|
|
'type' => 'integer',
|
|
'description' => 'IGNY8 content ID'
|
|
),
|
|
'task_id' => array(
|
|
'required' => false,
|
|
'type' => 'integer',
|
|
'description' => 'IGNY8 task ID'
|
|
)
|
|
)
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Check API permission - uses API key only
|
|
*
|
|
* @param WP_REST_Request $request Request object
|
|
* @return bool|WP_Error
|
|
*/
|
|
public function check_permission($request) {
|
|
// Check if authenticated with IGNY8 via API key
|
|
$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;
|
|
}
|
|
}
|
|
|
|
// Check Authorization Bearer header
|
|
$auth_header = $request->get_header('Authorization');
|
|
if ($auth_header) {
|
|
$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 && strpos($auth_header, 'Bearer ' . $stored_api_key) !== false) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Allow if API key is configured (for internal use)
|
|
if ($api->is_authenticated()) {
|
|
return true;
|
|
}
|
|
|
|
return new WP_Error(
|
|
'rest_forbidden',
|
|
__('IGNY8 API key not authenticated', 'igny8-bridge'),
|
|
array('status' => 401)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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 post ID or content_id
|
|
* Accepts either WordPress post_id or IGNY8 content_id
|
|
*
|
|
* @param WP_REST_Request $request Request object
|
|
* @return WP_REST_Response|WP_Error
|
|
*/
|
|
public function get_post_status($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)
|
|
);
|
|
}
|
|
|
|
$id = intval($request['id']);
|
|
$post = null;
|
|
$lookup_method = null;
|
|
|
|
// First try as WordPress post ID
|
|
if (post_type_exists('post') || post_type_exists('page')) {
|
|
$post = get_post($id);
|
|
if ($post) {
|
|
$lookup_method = 'wordpress_post_id';
|
|
}
|
|
}
|
|
|
|
// If not found, try as IGNY8 content_id
|
|
if (!$post) {
|
|
$posts = get_posts(array(
|
|
'meta_key' => '_igny8_content_id',
|
|
'meta_value' => $id,
|
|
'post_type' => 'any',
|
|
'posts_per_page' => 1,
|
|
'post_status' => 'any'
|
|
));
|
|
|
|
if (!empty($posts)) {
|
|
$post = $posts[0];
|
|
$lookup_method = 'igny8_content_id';
|
|
}
|
|
}
|
|
|
|
if (!$post) {
|
|
return rest_ensure_response(array(
|
|
'success' => false,
|
|
'message' => 'Post not found',
|
|
'searched_id' => $id
|
|
));
|
|
}
|
|
|
|
return rest_ensure_response(array(
|
|
'success' => true,
|
|
'data' => array(
|
|
'post_id' => $post->ID,
|
|
'post_status' => $post->post_status,
|
|
'post_title' => $post->post_title,
|
|
'post_type' => $post->post_type,
|
|
'post_modified' => $post->post_modified,
|
|
'post_url' => get_permalink($post->ID),
|
|
'wordpress_status' => $post->post_status,
|
|
'igny8_status' => igny8_map_wp_status_to_igny8($post->post_status),
|
|
'content_id' => get_post_meta($post->ID, '_igny8_content_id', true),
|
|
'task_id' => get_post_meta($post->ID, '_igny8_task_id', true),
|
|
'last_synced' => get_post_meta($post->ID, '_igny8_last_synced', true),
|
|
'lookup_method' => $lookup_method
|
|
)
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Get post status by content_id (DEPRECATED - use get_post_status instead)
|
|
*
|
|
* @param WP_REST_Request $request Request object
|
|
* @return WP_REST_Response|WP_Error
|
|
*/
|
|
public function get_post_status_by_content_id($request) {
|
|
// Redirect to new unified method
|
|
return $this->get_post_status($request);
|
|
}
|
|
|
|
/**
|
|
* 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 /status - Returns plugin connection status and API key info
|
|
*
|
|
* @param WP_REST_Request $request
|
|
* @return WP_REST_Response
|
|
*/
|
|
public function get_status($request) {
|
|
$api = new Igny8API();
|
|
$api_key = function_exists('igny8_get_secure_option') ? igny8_get_secure_option('igny8_api_key') : get_option('igny8_api_key');
|
|
$connection_enabled = igny8_is_connection_enabled();
|
|
|
|
$data = array(
|
|
'connected' => !empty($api_key) && $api->is_authenticated(),
|
|
'has_api_key' => !empty($api_key),
|
|
'communication_enabled' => $connection_enabled,
|
|
'plugin_version' => defined('IGNY8_BRIDGE_VERSION') ? IGNY8_BRIDGE_VERSION : '1.0.0',
|
|
'wordpress_version' => get_bloginfo('version'),
|
|
'last_health_check' => get_option('igny8_last_api_health_check', 0),
|
|
'health' => (!empty($api_key) && $connection_enabled) ? 'healthy' : 'not_configured'
|
|
);
|
|
|
|
return $this->build_unified_response(true, $data, 'Plugin status retrieved', null, null, 200);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* Publish content to WordPress
|
|
*
|
|
* @param WP_REST_Request $request Request object
|
|
* @return WP_REST_Response|WP_Error
|
|
*/
|
|
public function publish_content_to_wordpress($request) {
|
|
// Check connection
|
|
if (!igny8_is_connection_enabled()) {
|
|
return $this->build_unified_response(
|
|
false,
|
|
null,
|
|
'IGNY8 connection is disabled',
|
|
'connection_disabled',
|
|
null,
|
|
403
|
|
);
|
|
}
|
|
|
|
// DIAGNOSTIC: Log raw request body
|
|
$raw_body = $request->get_body();
|
|
error_log('========== RAW REQUEST BODY ==========');
|
|
error_log($raw_body);
|
|
error_log('======================================');
|
|
|
|
// Get content data from POST body (IGNY8 backend already sends everything)
|
|
$content_data = $request->get_json_params();
|
|
|
|
// DIAGNOSTIC: Log parsed JSON
|
|
error_log('========== PARSED JSON DATA ==========');
|
|
error_log(print_r($content_data, true));
|
|
error_log('======================================');
|
|
|
|
// Extract IDs for validation
|
|
$content_id = isset($content_data['content_id']) ? $content_data['content_id'] : null;
|
|
$task_id = isset($content_data['task_id']) ? $content_data['task_id'] : null;
|
|
|
|
// ALWAYS log incoming data for debugging
|
|
error_log('========== IGNY8 PUBLISH REQUEST ==========');
|
|
error_log('Content ID: ' . $content_id);
|
|
error_log('Task ID: ' . $task_id);
|
|
error_log('Title: ' . (isset($content_data['title']) ? $content_data['title'] : 'MISSING'));
|
|
error_log('Content HTML: ' . (isset($content_data['content_html']) ? strlen($content_data['content_html']) . ' chars' : 'MISSING'));
|
|
error_log('Categories: ' . (isset($content_data['categories']) ? json_encode($content_data['categories']) : 'MISSING'));
|
|
error_log('Tags: ' . (isset($content_data['tags']) ? json_encode($content_data['tags']) : 'MISSING'));
|
|
error_log('Featured Image: ' . (isset($content_data['featured_image_url']) ? $content_data['featured_image_url'] : 'MISSING'));
|
|
error_log('Gallery Images: ' . (isset($content_data['gallery_images']) ? count($content_data['gallery_images']) . ' images' : 'MISSING'));
|
|
error_log('SEO Title: ' . (isset($content_data['seo_title']) ? 'YES' : 'NO'));
|
|
error_log('SEO Description: ' . (isset($content_data['seo_description']) ? 'YES' : 'NO'));
|
|
error_log('Primary Keyword: ' . (isset($content_data['primary_keyword']) ? $content_data['primary_keyword'] : 'MISSING'));
|
|
error_log('===========================================');
|
|
|
|
// Validate required fields
|
|
if (empty($content_id)) {
|
|
return $this->build_unified_response(
|
|
false,
|
|
null,
|
|
'Missing content_id in request',
|
|
'missing_content_id',
|
|
null,
|
|
400
|
|
);
|
|
}
|
|
|
|
if (empty($content_data['title'])) {
|
|
return $this->build_unified_response(
|
|
false,
|
|
null,
|
|
'Missing title in request',
|
|
'missing_title',
|
|
null,
|
|
400
|
|
);
|
|
}
|
|
|
|
if (empty($content_data['content_html'])) {
|
|
return $this->build_unified_response(
|
|
false,
|
|
null,
|
|
'Missing content_html in request',
|
|
'missing_content_html',
|
|
null,
|
|
400
|
|
);
|
|
}
|
|
|
|
// Debug logging
|
|
if (defined('IGNY8_DEBUG') && IGNY8_DEBUG) {
|
|
error_log('IGNY8 Publish Request - Content ID: ' . $content_id);
|
|
error_log('IGNY8 Publish Request - Title: ' . $content_data['title']);
|
|
error_log('IGNY8 Publish Request - Content HTML length: ' . strlen($content_data['content_html']));
|
|
}
|
|
|
|
// Check if content already exists
|
|
$existing_posts = get_posts(array(
|
|
'meta_key' => '_igny8_content_id',
|
|
'meta_value' => $content_id,
|
|
'post_type' => 'any',
|
|
'posts_per_page' => 1
|
|
));
|
|
|
|
if (!empty($existing_posts)) {
|
|
return $this->build_unified_response(
|
|
false,
|
|
array('post_id' => $existing_posts[0]->ID),
|
|
'Content already exists as WordPress post',
|
|
'content_exists',
|
|
null,
|
|
409
|
|
);
|
|
}
|
|
|
|
// Create WordPress post
|
|
$post_id = igny8_create_wordpress_post_from_task($content_data);
|
|
|
|
if (is_wp_error($post_id)) {
|
|
return $this->build_unified_response(
|
|
false,
|
|
null,
|
|
'Failed to create WordPress post: ' . $post_id->get_error_message(),
|
|
'post_creation_failed',
|
|
null,
|
|
500
|
|
);
|
|
}
|
|
|
|
// Return success response
|
|
return $this->build_unified_response(
|
|
true,
|
|
array(
|
|
'post_id' => $post_id,
|
|
'post_url' => get_permalink($post_id),
|
|
'post_status' => get_post_status($post_id),
|
|
'content_id' => $content_id,
|
|
'task_id' => $task_id
|
|
),
|
|
'Content successfully published to WordPress',
|
|
null,
|
|
null,
|
|
201
|
|
);
|
|
}
|
|
}
|
|
|
|
// Initialize REST API
|
|
new Igny8RestAPI();
|
|
|