59 KiB
WordPress Plugin Integration Guide
Version: 1.0.0
Last Updated: 2025-01-XX (Added Publisher module endpoints for content publishing)
Complete guide for integrating WordPress plugins with IGNY8 API v1.0.
Overview
This guide helps WordPress plugin developers integrate with the IGNY8 API using the unified response format.
Authentication
Getting Access Token
function igny8_login($email, $password) {
$response = wp_remote_post('https://api.igny8.com/api/v1/auth/login/', [
'headers' => [
'Content-Type' => 'application/json'
],
'body' => json_encode([
'email' => $email,
'password' => $password
])
]);
$body = json_decode(wp_remote_retrieve_body($response), true);
if ($body['success']) {
// Store tokens
update_option('igny8_access_token', $body['data']['access']);
update_option('igny8_refresh_token', $body['data']['refresh']);
return $body['data']['access'];
} else {
return new WP_Error('login_failed', $body['error']);
}
}
Using Access Token
function igny8_get_headers() {
$token = get_option('igny8_access_token');
if (!$token) {
return false;
}
return [
'Authorization' => 'Bearer ' . $token,
'Content-Type' => 'application/json'
];
}
Note (required): To establish a bridge connection the plugin now requires three credentials to be provided and saved in the WordPress settings: Email, Password, and API Key. All three are stored as options (igny8_email, igny8_access_token/igny8_refresh_token, igny8_api_key) — the API key is stored using secure storage helpers when available. The bridge will refuse connection if any of these are missing.
API Client Class
Complete PHP Implementation
class Igny8API {
private $base_url = 'https://api.igny8.com/api/v1';
private $access_token = null;
private $refresh_token = null;
public function __construct() {
$this->access_token = get_option('igny8_access_token');
$this->refresh_token = get_option('igny8_refresh_token');
}
/**
* Login and store tokens
*/
public function login($email, $password) {
$response = wp_remote_post($this->base_url . '/auth/login/', [
'headers' => [
'Content-Type' => 'application/json'
],
'body' => json_encode([
'email' => $email,
'password' => $password
])
]);
$body = $this->parse_response($response);
if ($body['success']) {
$this->access_token = $body['data']['access'];
$this->refresh_token = $body['data']['refresh'];
update_option('igny8_access_token', $this->access_token);
update_option('igny8_refresh_token', $this->refresh_token);
return true;
}
return false;
}
/**
* Refresh access token
*/
public function refresh_token() {
if (!$this->refresh_token) {
return false;
}
$response = wp_remote_post($this->base_url . '/auth/refresh/', [
'headers' => [
'Content-Type' => 'application/json'
],
'body' => json_encode([
'refresh' => $this->refresh_token
])
]);
$body = $this->parse_response($response);
if ($body['success']) {
$this->access_token = $body['data']['access'];
$this->refresh_token = $body['data']['refresh'];
update_option('igny8_access_token', $this->access_token);
update_option('igny8_refresh_token', $this->refresh_token);
return true;
}
return false;
}
/**
* Parse unified API response
*/
private function parse_response($response) {
if (is_wp_error($response)) {
return [
'success' => false,
'error' => $response->get_error_message()
];
}
$body = json_decode(wp_remote_retrieve_body($response), true);
$status_code = wp_remote_retrieve_response_code($response);
// Handle non-JSON responses
if (!$body) {
return [
'success' => false,
'error' => 'Invalid response format'
];
}
// Check if response follows unified format
if (isset($body['success'])) {
return $body;
}
// Legacy format - wrap in unified format
if ($status_code >= 200 && $status_code < 300) {
return [
'success' => true,
'data' => $body
];
} else {
return [
'success' => false,
'error' => $body['detail'] ?? 'Unknown error'
];
}
}
/**
* Get headers with authentication
*/
private function get_headers() {
if (!$this->access_token) {
throw new Exception('Not authenticated');
}
return [
'Authorization' => 'Bearer ' . $this->access_token,
'Content-Type' => 'application/json'
];
}
/**
* Make GET request
*/
public function get($endpoint) {
$response = wp_remote_get($this->base_url . $endpoint, [
'headers' => $this->get_headers()
]);
$body = $this->parse_response($response);
// Handle 401 - token expired
if (!$body['success'] && wp_remote_retrieve_response_code($response) == 401) {
// Try to refresh token
if ($this->refresh_token()) {
// Retry request
$response = wp_remote_get($this->base_url . $endpoint, [
'headers' => $this->get_headers()
]);
$body = $this->parse_response($response);
}
}
return $body;
}
/**
* Make POST request
*/
public function post($endpoint, $data) {
$response = wp_remote_post($this->base_url . $endpoint, [
'headers' => $this->get_headers(),
'body' => json_encode($data)
]);
$body = $this->parse_response($response);
// Handle 401 - token expired
if (!$body['success'] && wp_remote_retrieve_response_code($response) == 401) {
// Try to refresh token
if ($this->refresh_token()) {
// Retry request
$response = wp_remote_post($this->base_url . $endpoint, [
'headers' => $this->get_headers(),
'body' => json_encode($data)
]);
$body = $this->parse_response($response);
}
}
return $body;
}
/**
* Make PUT request
*/
public function put($endpoint, $data) {
$response = wp_remote_request($this->base_url . $endpoint, [
'method' => 'PUT',
'headers' => $this->get_headers(),
'body' => json_encode($data)
]);
return $this->parse_response($response);
}
/**
* Make DELETE request
*/
public function delete($endpoint) {
$response = wp_remote_request($this->base_url . $endpoint, [
'method' => 'DELETE',
'headers' => $this->get_headers()
]);
return $this->parse_response($response);
}
}
Usage Examples
Get Keywords
$api = new Igny8API();
// Get keywords
$response = $api->get('/planner/keywords/');
if ($response['success']) {
$keywords = $response['results'];
$count = $response['count'];
foreach ($keywords as $keyword) {
echo $keyword['name'] . '<br>';
}
} else {
echo 'Error: ' . $response['error'];
}
Create Keyword
$api = new Igny8API();
$data = [
'seed_keyword_id' => 1,
'site_id' => 1,
'sector_id' => 1,
'status' => 'active'
];
$response = $api->post('/planner/keywords/', $data);
if ($response['success']) {
$keyword = $response['data'];
echo 'Created keyword: ' . $keyword['id'];
} else {
echo 'Error: ' . $response['error'];
if (isset($response['errors'])) {
foreach ($response['errors'] as $field => $errors) {
echo $field . ': ' . implode(', ', $errors) . '<br>';
}
}
}
Handle Pagination
$api = new Igny8API();
function get_all_keywords($api) {
$all_keywords = [];
$page = 1;
do {
$response = $api->get("/planner/keywords/?page={$page}&page_size=100");
if ($response['success']) {
$all_keywords = array_merge($all_keywords, $response['results']);
$page++;
} else {
break;
}
} while ($response['next']);
return $all_keywords;
}
$keywords = get_all_keywords($api);
Handle Rate Limiting
function make_rate_limited_request($api, $endpoint, $max_retries = 3) {
for ($attempt = 0; $attempt < $max_retries; $attempt++) {
$response = $api->get($endpoint);
// Check if rate limited
if (!$response['success'] && isset($response['error'])) {
if (strpos($response['error'], 'Rate limit') !== false) {
// Wait before retry
sleep(pow(2, $attempt)); // Exponential backoff
continue;
}
}
return $response;
}
return ['success' => false, 'error' => 'Max retries exceeded'];
}
Error Handling
Unified Error Handling
function handle_api_response($response) {
if ($response['success']) {
return $response['data'] ?? $response['results'];
} else {
$error_message = $response['error'];
// Log error with request ID
error_log(sprintf(
'IGNY8 API Error: %s (Request ID: %s)',
$error_message,
$response['request_id'] ?? 'unknown'
));
// Handle field-specific errors
if (isset($response['errors'])) {
foreach ($response['errors'] as $field => $errors) {
error_log(" {$field}: " . implode(', ', $errors));
}
}
return new WP_Error('igny8_api_error', $error_message, $response);
}
}
Best Practices
1. Store Tokens Securely
// Use WordPress options API with encryption
function save_token($token) {
// Encrypt token before storing
$encrypted = base64_encode($token);
update_option('igny8_access_token', $encrypted, false);
}
function get_token() {
$encrypted = get_option('igny8_access_token');
return base64_decode($encrypted);
}
2. Implement Token Refresh
function ensure_valid_token($api) {
// Check if token is about to expire (refresh 1 minute before)
// Token expires in 15 minutes, refresh at 14 minutes
$last_refresh = get_option('igny8_token_refreshed_at', 0);
if (time() - $last_refresh > 14 * 60) {
if ($api->refresh_token()) {
update_option('igny8_token_refreshed_at', time());
}
}
}
3. Cache Responses
function get_cached_keywords($api, $cache_key = 'igny8_keywords', $ttl = 300) {
$cached = get_transient($cache_key);
if ($cached !== false) {
return $cached;
}
$response = $api->get('/planner/keywords/');
if ($response['success']) {
$keywords = $response['results'];
set_transient($cache_key, $keywords, $ttl);
return $keywords;
}
return false;
}
4. Handle Rate Limits
function check_rate_limit($response) {
// Note: WordPress wp_remote_* doesn't expose all headers easily
// Consider using cURL or checking response for 429 status
if (isset($response['error']) && strpos($response['error'], 'Rate limit') !== false) {
// Wait and retry
sleep(60);
return true; // Should retry
}
return false;
}
WordPress Admin Integration
Settings Page
function igny8_settings_page() {
?>
<div class="wrap">
<h1>IGNY8 API Settings</h1>
<form method="post" action="options.php">
<?php settings_fields('igny8_settings'); ?>
<table class="form-table">
<tr>
<th>API Email</th>
<td><input type="email" name="igny8_email" value="<?php echo get_option('igny8_email'); ?>" /></td>
</tr>
<tr>
<th>API Password</th>
<td><input type="password" name="igny8_password" value="" /></td>
</tr>
</table>
<?php submit_button('Save & Connect'); ?>
</form>
</div>
<?php
}
function igny8_save_settings() {
if (isset($_POST['igny8_email']) && isset($_POST['igny8_password'])) {
$api = new Igny8API();
if ($api->login($_POST['igny8_email'], $_POST['igny8_password'])) {
update_option('igny8_email', $_POST['igny8_email']);
add_settings_error('igny8_settings', 'igny8_connected', 'Successfully connected to IGNY8 API', 'updated');
} else {
add_settings_error('igny8_settings', 'igny8_error', 'Failed to connect to IGNY8 API', 'error');
}
}
}
add_action('admin_init', 'igny8_save_settings');
Testing
Unit Tests
class TestIgny8API extends WP_UnitTestCase {
public function test_login() {
$api = new Igny8API();
$result = $api->login('test@example.com', 'password');
$this->assertTrue($result);
$this->assertNotEmpty(get_option('igny8_access_token'));
}
public function test_get_keywords() {
$api = new Igny8API();
$response = $api->get('/planner/keywords/');
$this->assertTrue($response['success']);
$this->assertArrayHasKey('results', $response);
$this->assertArrayHasKey('count', $response);
}
}
Troubleshooting
Issue: Authentication Fails
Check:
- Email and password are correct
- Account is active
- API endpoint is accessible
Issue: Token Expires Frequently
Solution: Implement automatic token refresh before expiration.
Issue: Rate Limited
Solution: Implement request throttling and caching.
WordPress Hooks and Two-Way Sync
Overview
The integration supports two-way synchronization:
- IGNY8 → WordPress: Publishing content from IGNY8 to WordPress
- WordPress → IGNY8: Syncing WordPress post status changes back to IGNY8
WordPress Post Hooks
1. Post Save Hook (save_post)
Hook into WordPress post saves to sync status back to IGNY8:
/**
* Sync WordPress post status to IGNY8 when post is saved
*/
add_action('save_post', 'igny8_sync_post_status_to_igny8', 10, 3);
function igny8_sync_post_status_to_igny8($post_id, $post, $update) {
// Skip autosaves and revisions
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
if (wp_is_post_revision($post_id)) {
return;
}
// Only sync IGNY8-managed posts
$task_id = get_post_meta($post_id, '_igny8_task_id', true);
if (!$task_id) {
return;
}
// Get post status
$post_status = $post->post_status;
// Map WordPress status to IGNY8 task status
$task_status_map = [
'publish' => 'completed',
'draft' => 'draft',
'pending' => 'pending',
'private' => 'completed',
'trash' => 'archived'
];
$task_status = $task_status_map[$post_status] ?? 'draft';
// Sync to IGNY8 API
$api = new Igny8API();
$response = $api->put("/writer/tasks/{$task_id}/", [
'status' => $task_status,
'assigned_post_id' => $post_id,
'post_url' => get_permalink($post_id)
]);
if ($response['success']) {
error_log("IGNY8: Synced post {$post_id} status to task {$task_id}");
} else {
error_log("IGNY8: Failed to sync post status: " . $response['error']);
}
}
2. Post Publish Hook (publish_post)
Update keyword status when content is published:
/**
* Update keyword status when WordPress post is published
*/
add_action('publish_post', 'igny8_update_keywords_on_post_publish', 10, 1);
add_action('publish_page', 'igny8_update_keywords_on_post_publish', 10, 1);
add_action('draft_to_publish', 'igny8_update_keywords_on_post_publish', 10, 1);
add_action('future_to_publish', 'igny8_update_keywords_on_post_publish', 10, 1);
function igny8_update_keywords_on_post_publish($post_id) {
// Get task ID from post meta
$task_id = get_post_meta($post_id, '_igny8_task_id', true);
if (!$task_id) {
return;
}
$api = new Igny8API();
// Get task details to find associated cluster/keywords
$task_response = $api->get("/writer/tasks/{$task_id}/");
if (!$task_response['success']) {
return;
}
$task = $task_response['data'];
$cluster_id = $task['cluster_id'] ?? null;
if ($cluster_id) {
// Get keywords in this cluster
$keywords_response = $api->get("/planner/keywords/?cluster_id={$cluster_id}");
if ($keywords_response['success']) {
$keywords = $keywords_response['results'];
// Update each keyword status to 'mapped'
foreach ($keywords as $keyword) {
$api->put("/planner/keywords/{$keyword['id']}/", [
'status' => 'mapped'
]);
}
}
}
// Update task status to completed
$api->put("/writer/tasks/{$task_id}/", [
'status' => 'completed',
'assigned_post_id' => $post_id,
'post_url' => get_permalink($post_id)
]);
}
3. Post Status Change Hook (transition_post_status)
Handle all post status transitions:
/**
* Sync post status changes to IGNY8
*/
add_action('transition_post_status', 'igny8_sync_post_status_transition', 10, 3);
function igny8_sync_post_status_transition($new_status, $old_status, $post) {
// Skip if status hasn't changed
if ($new_status === $old_status) {
return;
}
// Only sync IGNY8-managed posts
$task_id = get_post_meta($post->ID, '_igny8_task_id', true);
if (!$task_id) {
return;
}
$api = new Igny8API();
// Map WordPress status to IGNY8 task status
$status_map = [
'publish' => 'completed',
'draft' => 'draft',
'pending' => 'pending',
'private' => 'completed',
'trash' => 'archived',
'future' => 'scheduled'
];
$task_status = $status_map[$new_status] ?? 'draft';
// Sync to IGNY8
$response = $api->put("/writer/tasks/{$task_id}/", [
'status' => $task_status,
'assigned_post_id' => $post->ID,
'post_url' => get_permalink($post->ID)
]);
if ($response['success']) {
do_action('igny8_post_status_synced', $post->ID, $task_id, $new_status);
}
}
Fetching WordPress Post Status
Get Post Status from WordPress
/**
* Get WordPress post status and sync to IGNY8
*/
function igny8_fetch_and_sync_post_status($post_id) {
$post = get_post($post_id);
if (!$post) {
return false;
}
// Get post status
$wp_status = $post->post_status;
// Get additional post data
$post_data = [
'id' => $post_id,
'status' => $wp_status,
'title' => $post->post_title,
'url' => get_permalink($post_id),
'modified' => $post->post_modified,
'published' => $post->post_date
];
// Get task ID
$task_id = get_post_meta($post_id, '_igny8_task_id', true);
if (!$task_id) {
return false;
}
// Sync to IGNY8
$api = new Igny8API();
// Map WordPress status to IGNY8 status
$status_map = [
'publish' => 'completed',
'draft' => 'draft',
'pending' => 'pending',
'private' => 'completed',
'trash' => 'archived'
];
$task_status = $status_map[$wp_status] ?? 'draft';
$response = $api->put("/writer/tasks/{$task_id}/", [
'status' => $task_status,
'assigned_post_id' => $post_id,
'post_url' => $post_data['url']
]);
return $response['success'];
}
Batch Sync Post Statuses
/**
* Sync all IGNY8-managed posts status to IGNY8 API
*/
function igny8_batch_sync_post_statuses() {
global $wpdb;
// Get all posts with IGNY8 task ID
$posts = $wpdb->get_results("
SELECT p.ID, p.post_status, p.post_title, pm.meta_value as task_id
FROM {$wpdb->posts} p
INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
WHERE pm.meta_key = '_igny8_task_id'
AND p.post_type IN ('post', 'page')
AND p.post_status != 'trash'
");
$api = new Igny8API();
$synced = 0;
$failed = 0;
foreach ($posts as $post_data) {
$post_id = $post_data->ID;
$task_id = intval($post_data->task_id);
$wp_status = $post_data->post_status;
// Map status
$status_map = [
'publish' => 'completed',
'draft' => 'draft',
'pending' => 'pending',
'private' => 'completed'
];
$task_status = $status_map[$wp_status] ?? 'draft';
// Sync to IGNY8
$response = $api->put("/writer/tasks/{$task_id}/", [
'status' => $task_status,
'assigned_post_id' => $post_id,
'post_url' => get_permalink($post_id)
]);
if ($response['success']) {
$synced++;
} else {
$failed++;
error_log("IGNY8: Failed to sync post {$post_id}: " . $response['error']);
}
}
return [
'synced' => $synced,
'failed' => $failed,
'total' => count($posts)
];
}
Complete Two-Way Sync Example
/**
* Complete two-way sync implementation
*/
class Igny8WordPressSync {
private $api;
public function __construct() {
$this->api = new Igny8API();
// WordPress → IGNY8 hooks
add_action('save_post', [$this, 'sync_post_to_igny8'], 10, 3);
add_action('publish_post', [$this, 'update_keywords_on_publish'], 10, 1);
add_action('transition_post_status', [$this, 'sync_status_transition'], 10, 3);
// IGNY8 → WordPress (when content is published from IGNY8)
add_action('igny8_content_published', [$this, 'create_wordpress_post'], 10, 1);
}
/**
* WordPress → IGNY8: Sync post changes to IGNY8
*/
public function sync_post_to_igny8($post_id, $post, $update) {
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
if (wp_is_post_revision($post_id)) {
return;
}
$task_id = get_post_meta($post_id, '_igny8_task_id', true);
if (!$task_id) {
return;
}
$status_map = [
'publish' => 'completed',
'draft' => 'draft',
'pending' => 'pending',
'private' => 'completed',
'trash' => 'archived'
];
$task_status = $status_map[$post->post_status] ?? 'draft';
$response = $this->api->put("/writer/tasks/{$task_id}/", [
'status' => $task_status,
'assigned_post_id' => $post_id,
'post_url' => get_permalink($post_id)
]);
if ($response['success']) {
error_log("IGNY8: Synced post {$post_id} to task {$task_id}");
}
}
/**
* WordPress → IGNY8: Update keywords when post is published
*/
public function update_keywords_on_publish($post_id) {
$task_id = get_post_meta($post_id, '_igny8_task_id', true);
if (!$task_id) {
return;
}
// Get task to find cluster
$task_response = $this->api->get("/writer/tasks/{$task_id}/");
if (!$task_response['success']) {
return;
}
$task = $task_response['data'];
$cluster_id = $task['cluster_id'] ?? null;
if ($cluster_id) {
// Update keywords in cluster to 'mapped'
$keywords_response = $this->api->get("/planner/keywords/?cluster_id={$cluster_id}");
if ($keywords_response['success']) {
foreach ($keywords_response['results'] as $keyword) {
$this->api->put("/planner/keywords/{$keyword['id']}/", [
'status' => 'mapped'
]);
}
}
}
// Update task status
$this->api->put("/writer/tasks/{$task_id}/", [
'status' => 'completed',
'assigned_post_id' => $post_id,
'post_url' => get_permalink($post_id)
]);
}
/**
* WordPress → IGNY8: Handle status transitions
*/
public function sync_status_transition($new_status, $old_status, $post) {
if ($new_status === $old_status) {
return;
}
$task_id = get_post_meta($post->ID, '_igny8_task_id', true);
if (!$task_id) {
return;
}
$status_map = [
'publish' => 'completed',
'draft' => 'draft',
'pending' => 'pending',
'private' => 'completed',
'trash' => 'archived'
];
$task_status = $status_map[$new_status] ?? 'draft';
$this->api->put("/writer/tasks/{$task_id}/", [
'status' => $task_status,
'assigned_post_id' => $post->ID,
'post_url' => get_permalink($post->ID)
]);
}
/**
* IGNY8 → WordPress: Create WordPress post from IGNY8 content
*/
public function create_wordpress_post($content_data) {
$post_data = [
'post_title' => $content_data['title'],
'post_content' => $content_data['content'],
'post_status' => $content_data['status'] ?? 'draft',
'post_type' => 'post',
'meta_input' => [
'_igny8_task_id' => $content_data['task_id'],
'_igny8_content_id' => $content_data['content_id']
]
];
$post_id = wp_insert_post($post_data);
if (!is_wp_error($post_id)) {
// Update IGNY8 task with WordPress post ID
$this->api->put("/writer/tasks/{$content_data['task_id']}/", [
'assigned_post_id' => $post_id,
'post_url' => get_permalink($post_id)
]);
}
return $post_id;
}
}
// Initialize sync
new Igny8WordPressSync();
WordPress Post Status Mapping
| WordPress Status | IGNY8 Task Status | Description |
|---|---|---|
publish |
completed |
Post is published |
draft |
draft |
Post is draft |
pending |
pending |
Post is pending review |
private |
completed |
Post is private (published) |
trash |
archived |
Post is deleted/trashed |
future |
scheduled |
Post is scheduled |
Fetching WordPress Post Data
/**
* Get WordPress post data for IGNY8 sync
*/
function igny8_get_post_data_for_sync($post_id) {
$post = get_post($post_id);
if (!$post) {
return false;
}
return [
'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' => [
'task_id' => get_post_meta($post_id, '_igny8_task_id', true),
'content_id' => get_post_meta($post_id, '_igny8_content_id', true),
'primary_keywords' => get_post_meta($post_id, '_igny8_primary_keywords', true)
]
];
}
Scheduled Sync (Cron Job)
/**
* Scheduled sync of WordPress post statuses to IGNY8
*/
add_action('igny8_sync_post_statuses', 'igny8_cron_sync_post_statuses');
function igny8_cron_sync_post_statuses() {
$result = igny8_batch_sync_post_statuses();
error_log(sprintf(
'IGNY8: Synced %d posts, %d failed',
$result['synced'],
$result['failed']
));
}
// Schedule daily sync
if (!wp_next_scheduled('igny8_sync_post_statuses')) {
wp_schedule_event(time(), 'daily', 'igny8_sync_post_statuses');
}
Complete Integration Flow
IGNY8 → WordPress Flow (Using Publisher Module)
- Content generated in IGNY8
- Content created/updated in IGNY8
- Call
POST /api/v1/publisher/publish/withcontent_idanddestinations: ['wordpress'] - Publisher module creates WordPress post via REST API
- Publishing record created in IGNY8
- WordPress post ID stored in publishing record
- Content updated with WordPress post URL
IGNY8 → WordPress Flow (Legacy/Direct)
- Content generated in IGNY8
- Task created/updated in IGNY8
- WordPress post created via
wp_insert_post()(if using WordPress plugin) - Post meta saved with
_igny8_task_id - IGNY8 task updated with WordPress post ID
WordPress → IGNY8 Flow
- User saves/publishes WordPress post
save_postorpublish_posthook fires- Plugin gets
_igny8_task_idfrom post meta - Plugin calls IGNY8 API to update task status
- If published, keywords updated to 'mapped' status
- IGNY8 task status synced
WordPress Site Data Fetching and Semantic Mapping
Overview
After WordPress site integration and API verification, you can fetch comprehensive site data (posts, taxonomies, products, attributes) and send it to IGNY8 for semantic strategy mapping. This enables content restructuring and site-wide optimization.
Fetching WordPress Posts
Get All Post Types
/**
* Fetch all posts of a specific type from WordPress
*/
function igny8_fetch_wordpress_posts($post_type = 'post', $per_page = 100) {
$api = new Igny8API();
// Use WordPress REST API to fetch posts
$wp_response = wp_remote_get(sprintf(
'%s/wp-json/wp/v2/%s?per_page=%d&status=publish',
get_site_url(),
$post_type,
$per_page
));
if (is_wp_error($wp_response)) {
return false;
}
$posts = json_decode(wp_remote_retrieve_body($wp_response), true);
// Format posts for IGNY8
$formatted_posts = [];
foreach ($posts as $post) {
$formatted_posts[] = [
'id' => $post['id'],
'title' => $post['title']['rendered'],
'content' => $post['content']['rendered'],
'excerpt' => $post['excerpt']['rendered'],
'status' => $post['status'],
'url' => $post['link'],
'published' => $post['date'],
'modified' => $post['modified'],
'author' => $post['author'],
'featured_image' => $post['featured_media'] ? wp_get_attachment_url($post['featured_media']) : null,
'categories' => $post['categories'] ?? [],
'tags' => $post['tags'] ?? [],
'post_type' => $post_type,
'meta' => [
'word_count' => str_word_count(strip_tags($post['content']['rendered'])),
'reading_time' => ceil(str_word_count(strip_tags($post['content']['rendered'])) / 200)
]
];
}
return $formatted_posts;
}
Get All Post Types
/**
* Fetch all available post types from WordPress
*/
function igny8_fetch_all_post_types() {
$wp_response = wp_remote_get(get_site_url() . '/wp-json/wp/v2/types');
if (is_wp_error($wp_response)) {
return false;
}
$types = json_decode(wp_remote_retrieve_body($wp_response), true);
$post_types = [];
foreach ($types as $type_name => $type_data) {
if ($type_data['public']) {
$post_types[] = [
'name' => $type_name,
'label' => $type_data['name'],
'description' => $type_data['description'] ?? '',
'rest_base' => $type_data['rest_base'] ?? $type_name
];
}
}
return $post_types;
}
Batch Fetch All Posts
/**
* Fetch all posts from all post types
*/
function igny8_fetch_all_wordpress_posts() {
$post_types = igny8_fetch_all_post_types();
$all_posts = [];
foreach ($post_types as $type) {
$posts = igny8_fetch_wordpress_posts($type['name'], 100);
if ($posts) {
$all_posts = array_merge($all_posts, $posts);
}
}
return $all_posts;
}
Fetching WordPress Taxonomies
Get All Taxonomies
/**
* Fetch all taxonomies from WordPress
*/
function igny8_fetch_wordpress_taxonomies() {
$wp_response = wp_remote_get(get_site_url() . '/wp-json/wp/v2/taxonomies');
if (is_wp_error($wp_response)) {
return false;
}
$taxonomies = json_decode(wp_remote_retrieve_body($wp_response), true);
$formatted_taxonomies = [];
foreach ($taxonomies as $tax_name => $tax_data) {
if ($tax_data['public']) {
$formatted_taxonomies[] = [
'name' => $tax_name,
'label' => $tax_data['name'],
'description' => $tax_data['description'] ?? '',
'hierarchical' => $tax_data['hierarchical'],
'rest_base' => $tax_data['rest_base'] ?? $tax_name,
'object_types' => $tax_data['types'] ?? []
];
}
}
return $formatted_taxonomies;
}
Get Taxonomy Terms
/**
* Fetch all terms for a specific taxonomy
*/
function igny8_fetch_taxonomy_terms($taxonomy, $per_page = 100) {
$api = new Igny8API();
$wp_response = wp_remote_get(sprintf(
'%s/wp-json/wp/v2/%s?per_page=%d',
get_site_url(),
$taxonomy,
$per_page
));
if (is_wp_error($wp_response)) {
return false;
}
$terms = json_decode(wp_remote_retrieve_body($wp_response), true);
$formatted_terms = [];
foreach ($terms as $term) {
$formatted_terms[] = [
'id' => $term['id'],
'name' => $term['name'],
'slug' => $term['slug'],
'description' => $term['description'] ?? '',
'count' => $term['count'],
'parent' => $term['parent'] ?? 0,
'taxonomy' => $taxonomy,
'url' => $term['link']
];
}
return $formatted_terms;
}
Get All Taxonomy Terms
/**
* Fetch all terms from all taxonomies
*/
function igny8_fetch_all_taxonomy_terms() {
$taxonomies = igny8_fetch_wordpress_taxonomies();
$all_terms = [];
foreach ($taxonomies as $taxonomy) {
$terms = igny8_fetch_taxonomy_terms($taxonomy['rest_base'], 100);
if ($terms) {
$all_terms[$taxonomy['name']] = $terms;
}
}
return $all_terms;
}
Fetching WooCommerce Products
Get All Products
/**
* Fetch all WooCommerce products
*/
function igny8_fetch_woocommerce_products($per_page = 100) {
// Check if WooCommerce is active
if (!class_exists('WooCommerce')) {
return false;
}
$wp_response = wp_remote_get(sprintf(
'%s/wp-json/wc/v3/products?per_page=%d&status=publish',
get_site_url(),
$per_page
), [
'headers' => [
'Authorization' => 'Basic ' . base64_encode(get_option('woocommerce_api_consumer_key') . ':' . get_option('woocommerce_api_consumer_secret'))
]
]);
if (is_wp_error($wp_response)) {
return false;
}
$products = json_decode(wp_remote_retrieve_body($wp_response), true);
$formatted_products = [];
foreach ($products as $product) {
$formatted_products[] = [
'id' => $product['id'],
'name' => $product['name'],
'slug' => $product['slug'],
'sku' => $product['sku'],
'type' => $product['type'],
'status' => $product['status'],
'description' => $product['description'],
'short_description' => $product['short_description'],
'price' => $product['price'],
'regular_price' => $product['regular_price'],
'sale_price' => $product['sale_price'],
'on_sale' => $product['on_sale'],
'stock_status' => $product['stock_status'],
'stock_quantity' => $product['stock_quantity'],
'categories' => $product['categories'] ?? [],
'tags' => $product['tags'] ?? [],
'images' => $product['images'] ?? [],
'attributes' => $product['attributes'] ?? [],
'variations' => $product['variations'] ?? [],
'url' => $product['permalink']
];
}
return $formatted_products;
}
Get Product Categories
/**
* Fetch WooCommerce product categories
*/
function igny8_fetch_product_categories($per_page = 100) {
if (!class_exists('WooCommerce')) {
return false;
}
$wp_response = wp_remote_get(sprintf(
'%s/wp-json/wc/v3/products/categories?per_page=%d',
get_site_url(),
$per_page
), [
'headers' => [
'Authorization' => 'Basic ' . base64_encode(get_option('woocommerce_api_consumer_key') . ':' . get_option('woocommerce_api_consumer_secret'))
]
]);
if (is_wp_error($wp_response)) {
return false;
}
$categories = json_decode(wp_remote_retrieve_body($wp_response), true);
$formatted_categories = [];
foreach ($categories as $category) {
$formatted_categories[] = [
'id' => $category['id'],
'name' => $category['name'],
'slug' => $category['slug'],
'description' => $category['description'] ?? '',
'count' => $category['count'],
'parent' => $category['parent'] ?? 0,
'image' => $category['image']['src'] ?? null
];
}
return $formatted_categories;
}
Get Product Attributes
/**
* Fetch WooCommerce product attributes
*/
function igny8_fetch_product_attributes() {
if (!class_exists('WooCommerce')) {
return false;
}
$wp_response = wp_remote_get(
get_site_url() . '/wp-json/wc/v3/products/attributes',
[
'headers' => [
'Authorization' => 'Basic ' . base64_encode(get_option('woocommerce_api_consumer_key') . ':' . get_option('woocommerce_api_consumer_secret'))
]
]
);
if (is_wp_error($wp_response)) {
return false;
}
$attributes = json_decode(wp_remote_retrieve_body($wp_response), true);
$formatted_attributes = [];
foreach ($attributes as $attribute) {
// Get attribute terms
$terms_response = wp_remote_get(sprintf(
'%s/wp-json/wc/v3/products/attributes/%d/terms',
get_site_url(),
$attribute['id']
), [
'headers' => [
'Authorization' => 'Basic ' . base64_encode(get_option('woocommerce_api_consumer_key') . ':' . get_option('woocommerce_api_consumer_secret'))
]
]);
$terms = [];
if (!is_wp_error($terms_response)) {
$terms_data = json_decode(wp_remote_retrieve_body($terms_response), true);
foreach ($terms_data as $term) {
$terms[] = [
'id' => $term['id'],
'name' => $term['name'],
'slug' => $term['slug']
];
}
}
$formatted_attributes[] = [
'id' => $attribute['id'],
'name' => $attribute['name'],
'slug' => $attribute['slug'],
'type' => $attribute['type'],
'order_by' => $attribute['order_by'],
'has_archives' => $attribute['has_archives'],
'terms' => $terms
];
}
return $formatted_attributes;
}
Sending Site Data to IGNY8 for Semantic Mapping
Complete Site Data Collection
/**
* Collect all WordPress site data for IGNY8 semantic mapping
*/
function igny8_collect_site_data() {
$site_data = [
'site_url' => get_site_url(),
'site_name' => get_bloginfo('name'),
'site_description' => get_bloginfo('description'),
'collected_at' => current_time('mysql'),
'posts' => [],
'taxonomies' => [],
'products' => [],
'product_attributes' => []
];
// Fetch all posts
$post_types = igny8_fetch_all_post_types();
foreach ($post_types as $type) {
$posts = igny8_fetch_wordpress_posts($type['name'], 100);
if ($posts) {
$site_data['posts'] = array_merge($site_data['posts'], $posts);
}
}
// Fetch all taxonomies and terms
$taxonomies = igny8_fetch_wordpress_taxonomies();
foreach ($taxonomies as $taxonomy) {
$terms = igny8_fetch_taxonomy_terms($taxonomy['rest_base'], 100);
if ($terms) {
$site_data['taxonomies'][$taxonomy['name']] = [
'taxonomy' => $taxonomy,
'terms' => $terms
];
}
}
// Fetch WooCommerce products if available
if (class_exists('WooCommerce')) {
$products = igny8_fetch_woocommerce_products(100);
if ($products) {
$site_data['products'] = $products;
}
$product_categories = igny8_fetch_product_categories(100);
if ($product_categories) {
$site_data['product_categories'] = $product_categories;
}
$product_attributes = igny8_fetch_product_attributes();
if ($product_attributes) {
$site_data['product_attributes'] = $product_attributes;
}
}
return $site_data;
}
Send Site Data to IGNY8 API
/**
* Send WordPress site data to IGNY8 for semantic strategy mapping
*/
function igny8_send_site_data_to_igny8($site_id) {
$api = new Igny8API();
// Collect all site data
$site_data = igny8_collect_site_data();
// Send to IGNY8 API
// Note: This endpoint is planned/future feature - may not be available yet
// Alternative: Use Integration module endpoints for site data sync
$response = $api->post("/integration/integrations/{$integration_id}/sync/", [
'site_data' => $site_data,
'import_type' => 'full_site_scan'
]);
if ($response['success']) {
// Store import ID for tracking
update_option('igny8_last_site_import_id', $response['data']['import_id'] ?? null);
return $response['data'];
} else {
error_log("IGNY8: Failed to send site data: " . $response['error']);
return false;
}
}
Incremental Site Data Sync
/**
* Sync only changed posts/taxonomies since last sync
*/
function igny8_sync_incremental_site_data($site_id) {
$api = new Igny8API();
$last_sync = get_option('igny8_last_site_sync', 0);
// Fetch only posts modified since last sync
$wp_response = wp_remote_get(sprintf(
'%s/wp-json/wp/v2/posts?after=%s&per_page=100',
get_site_url(),
date('c', $last_sync)
));
if (is_wp_error($wp_response)) {
return false;
}
$posts = json_decode(wp_remote_retrieve_body($wp_response), true);
if (empty($posts)) {
return ['synced' => 0, 'message' => 'No changes since last sync'];
}
// Format posts
$formatted_posts = [];
foreach ($posts as $post) {
$formatted_posts[] = [
'id' => $post['id'],
'title' => $post['title']['rendered'],
'content' => $post['content']['rendered'],
'status' => $post['status'],
'modified' => $post['modified'],
'categories' => $post['categories'] ?? [],
'tags' => $post['tags'] ?? []
];
}
// Send incremental update to IGNY8
// Note: Use Integration module for site sync
$response = $api->post("/integration/integrations/{$integration_id}/sync/", [
'posts' => $formatted_posts,
'sync_type' => 'incremental',
'last_sync' => $last_sync
]);
if ($response['success']) {
update_option('igny8_last_site_sync', time());
return [
'synced' => count($formatted_posts),
'message' => 'Incremental sync completed'
];
}
return false;
}
Semantic Strategy Mapping
Map Site Data to IGNY8 Semantic Structure
/**
* Map WordPress site data to IGNY8 semantic strategy
* This creates sectors, clusters, and keywords based on site structure
*/
function igny8_map_site_to_semantic_strategy($site_id, $site_data) {
$api = new Igny8API();
// Extract semantic structure from site data
$semantic_map = [
'sectors' => [],
'clusters' => [],
'keywords' => []
];
// Map taxonomies to sectors
foreach ($site_data['taxonomies'] as $tax_name => $tax_data) {
if ($tax_data['taxonomy']['hierarchical']) {
// Hierarchical taxonomies (categories) become sectors
$sector = [
'name' => $tax_data['taxonomy']['label'],
'slug' => $tax_data['taxonomy']['name'],
'description' => $tax_data['taxonomy']['description'],
'source' => 'wordpress_taxonomy',
'source_id' => $tax_name
];
// Map terms to clusters
$clusters = [];
foreach ($tax_data['terms'] as $term) {
$clusters[] = [
'name' => $term['name'],
'slug' => $term['slug'],
'description' => $term['description'],
'source' => 'wordpress_term',
'source_id' => $term['id']
];
// Extract keywords from posts in this term
$keywords = igny8_extract_keywords_from_term_posts($term['id'], $tax_name);
$semantic_map['keywords'] = array_merge($semantic_map['keywords'], $keywords);
}
$sector['clusters'] = $clusters;
$semantic_map['sectors'][] = $sector;
}
}
// Map WooCommerce product categories to sectors
if (!empty($site_data['product_categories'])) {
$product_sector = [
'name' => 'Products',
'slug' => 'products',
'description' => 'WooCommerce product categories',
'source' => 'woocommerce',
'clusters' => []
];
foreach ($site_data['product_categories'] as $category) {
$product_sector['clusters'][] = [
'name' => $category['name'],
'slug' => $category['slug'],
'description' => $category['description'],
'source' => 'woocommerce_category',
'source_id' => $category['id']
];
}
$semantic_map['sectors'][] = $product_sector;
}
// Send semantic map to IGNY8
// Note: This endpoint is planned/future feature - may not be available yet
// Alternative: Use Planner module to create sectors/clusters/keywords manually
$response = $api->post("/planner/sites/{$site_id}/semantic-map/", [
'semantic_map' => $semantic_map,
'site_data' => $site_data
]);
return $response;
}
Extract Keywords from Posts
/**
* Extract keywords from posts associated with a taxonomy term
*/
function igny8_extract_keywords_from_term_posts($term_id, $taxonomy) {
$args = [
'post_type' => 'any',
'posts_per_page' => -1,
'tax_query' => [
[
'taxonomy' => $taxonomy,
'field' => 'term_id',
'terms' => $term_id
]
]
];
$query = new WP_Query($args);
$keywords = [];
if ($query->have_posts()) {
while ($query->have_posts()) {
$query->the_post();
// Extract keywords from post title and content
$title_words = str_word_count(get_the_title(), 1);
$content_words = str_word_count(strip_tags(get_the_content()), 1);
// Combine and get unique keywords
$all_words = array_merge($title_words, $content_words);
$unique_words = array_unique(array_map('strtolower', $all_words));
// Filter out common words (stop words)
$stop_words = ['the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by'];
$keywords = array_merge($keywords, array_diff($unique_words, $stop_words));
}
wp_reset_postdata();
}
// Format keywords
$formatted_keywords = [];
foreach (array_unique($keywords) as $keyword) {
if (strlen($keyword) > 3) { // Only keywords longer than 3 characters
$formatted_keywords[] = [
'keyword' => $keyword,
'source' => 'wordpress_post',
'source_term_id' => $term_id
];
}
}
return $formatted_keywords;
}
Content Restructuring Workflow
Complete Site Analysis and Restructuring
/**
* Complete workflow: Fetch site data → Map to semantic strategy → Restructure content
*/
function igny8_analyze_and_restructure_site($site_id) {
$api = new Igny8API();
// Step 1: Collect all site data
$site_data = igny8_collect_site_data();
// Step 2: Send to IGNY8 for analysis
// Note: This endpoint is planned/future feature - may not be available yet
$analysis_response = $api->post("/system/sites/{$site_id}/analyze/", [
'site_data' => $site_data,
'analysis_type' => 'full_site_restructure'
]);
if (!$analysis_response['success']) {
return false;
}
$analysis_id = $analysis_response['data']['analysis_id'];
// Step 3: Map to semantic strategy
$mapping_response = igny8_map_site_to_semantic_strategy($site_id, $site_data);
if (!$mapping_response['success']) {
return false;
}
// Step 4: Get restructuring recommendations
// Note: This endpoint is planned/future feature - may not be available yet
$recommendations_response = $api->get("/system/sites/{$site_id}/recommendations/");
if (!$recommendations_response['success']) {
return false;
}
return [
'analysis_id' => $analysis_id,
'semantic_map' => $mapping_response['data'],
'recommendations' => $recommendations_response['data'],
'site_data_summary' => [
'total_posts' => count($site_data['posts']),
'total_taxonomies' => count($site_data['taxonomies']),
'total_products' => count($site_data['products'] ?? []),
'total_keywords' => count($site_data['keywords'] ?? [])
]
];
}
Scheduled Site Data Sync
/**
* Scheduled sync of WordPress site data to IGNY8
*/
add_action('igny8_sync_site_data', 'igny8_cron_sync_site_data');
function igny8_cron_sync_site_data() {
$site_id = get_option('igny8_site_id');
if (!$site_id) {
return;
}
// Incremental sync
$result = igny8_sync_incremental_site_data($site_id);
if ($result) {
error_log(sprintf(
'IGNY8: Synced %d posts to site %d',
$result['synced'],
$site_id
));
}
}
// Schedule daily sync
if (!wp_next_scheduled('igny8_sync_site_data')) {
wp_schedule_event(time(), 'daily', 'igny8_sync_site_data');
}
Complete Site Integration Class
/**
* Complete WordPress site integration class
*/
class Igny8SiteIntegration {
private $api;
private $site_id;
public function __construct($site_id) {
$this->api = new Igny8API();
$this->site_id = $site_id;
}
/**
* Full site scan and semantic mapping
*/
public function full_site_scan() {
// Collect all data
$site_data = igny8_collect_site_data();
// Send to IGNY8
// Note: Use Integration module for site data import
$response = $this->api->post("/integration/integrations/{$integration_id}/sync/", [
'site_data' => $site_data,
'import_type' => 'full_scan'
]);
if ($response['success']) {
// Map to semantic strategy
$mapping = igny8_map_site_to_semantic_strategy($this->site_id, $site_data);
return [
'success' => true,
'import_id' => $response['data']['import_id'] ?? null,
'semantic_map' => $mapping['data'] ?? null,
'summary' => [
'posts' => count($site_data['posts']),
'taxonomies' => count($site_data['taxonomies']),
'products' => count($site_data['products'] ?? []),
'product_attributes' => count($site_data['product_attributes'] ?? [])
]
];
}
return ['success' => false, 'error' => $response['error']];
}
/**
* Get semantic strategy recommendations
*/
public function get_recommendations() {
// Note: This endpoint is planned/future feature - may not be available yet
$response = $this->api->get("/planner/sites/{$this->site_id}/recommendations/");
if ($response['success']) {
return $response['data'];
}
return false;
}
/**
* Apply restructuring recommendations
*/
public function apply_restructuring($recommendations) {
// Note: This endpoint is planned/future feature - may not be available yet
$response = $this->api->post("/planner/sites/{$this->site_id}/restructure/", [
'recommendations' => $recommendations
]);
return $response['success'];
}
}
// Usage
$integration = new Igny8SiteIntegration($site_id);
$result = $integration->full_site_scan();
if ($result['success']) {
echo "Scanned {$result['summary']['posts']} posts, {$result['summary']['taxonomies']} taxonomies";
// Get recommendations
$recommendations = $integration->get_recommendations();
// Apply restructuring
if ($recommendations) {
$integration->apply_restructuring($recommendations);
}
}
Data Structure Examples
Post Data Structure
[
'id' => 123,
'title' => 'Post Title',
'content' => 'Post content...',
'excerpt' => 'Post excerpt...',
'status' => 'publish',
'url' => 'https://example.com/post/',
'published' => '2025-01-01T00:00:00',
'modified' => '2025-01-02T00:00:00',
'author' => 1,
'featured_image' => 'https://example.com/image.jpg',
'categories' => [1, 2, 3],
'tags' => [4, 5],
'post_type' => 'post',
'meta' => [
'word_count' => 500,
'reading_time' => 3
]
]
Taxonomy Structure
[
'taxonomy' => [
'name' => 'category',
'label' => 'Categories',
'hierarchical' => true,
'object_types' => ['post']
],
'terms' => [
[
'id' => 1,
'name' => 'Technology',
'slug' => 'technology',
'description' => 'Tech posts',
'count' => 25,
'parent' => 0
]
]
]
Product Structure
[
'id' => 456,
'name' => 'Product Name',
'sku' => 'PROD-123',
'type' => 'simple',
'price' => '29.99',
'categories' => [10, 11],
'tags' => [20],
'attributes' => [
[
'id' => 1,
'name' => 'Color',
'options' => ['Red', 'Blue']
]
],
'variations' => [789, 790]
]
Last Updated: 2025-11-16
API Version: 1.0.0
Site Metadata Endpoint (Bridge)
The WordPress Bridge exposes a discovery endpoint that the IGNY8 SaaS app can call to retrieve available post types, taxonomies and counts. The endpoint follows the IGNY8 unified response format and includes plugin-side flags so the SaaS app can decide whether to perform automatic syncs.
- URL:
GET /wp-json/igny8/v1/site-metadata/ - Authentication: Plugin-side connection must be authenticated. The bridge accepts:
Authorization: Bearer <token_or_api_key>(preferred)X-IGNY8-API-KEY: <api_key>(supported) The plugin stores an optional API key inigny8_api_key(secure storage viaigny8_store_secure_option()when available) and will use it as the active access token when present.
- Response shape: IGNY8 unified format — top-level
success,data, optionalmessage, andrequest_id. - Caching: Results are cached on the WP side using a transient (
igny8_site_metadata_v1) with a default TTL of 300 seconds.
Behavior notes:
- Toggling "Enable Sync Operations" (option
igny8_connection_enabled) or "Enable Two-Way Sync" (igny8_enable_two_way_sync) in the plugin admin only affects background/inbound/outbound sync actions. REST discovery endpoints remain accessible so the SaaS app can still query metadata even when sync is disabled on either side. - The endpoint includes two flags in the payload:
plugin_connection_enabledandtwo_way_sync_enabledso the SaaS app can make informed decisions.
Example response:
{
"success": true,
"data": {
"post_types": {
"post": { "label": "Posts", "count": 123 },
"page": { "label": "Pages", "count": 12 }
},
"taxonomies": {
"category": { "label": "Categories", "count": 25 },
"post_tag": { "label": "Tags", "count": 102 }
},
"generated_at": 1700553600,
"plugin_connection_enabled": true,
"two_way_sync_enabled": true
},
"message": "Site metadata retrieved",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
Developer notes:
- Admin settings:
- API key input (option
igny8_api_key) added to Settings → IGNY8 API. When provided the key is stored securely and used as the access token by the plugin. - Two-way sync toggle added (option
igny8_enable_two_way_sync) — default ON. - Connection toggle
igny8_connection_enabledremains the master on/off switch for sync operations.
- API key input (option
- Tests: Basic unit test added at
igny8-wp-integration-plugin/tests/test-site-metadata.php. - Security: Webhook secret and REST permission checks remain enforced; unauthenticated REST calls still return unified 401 errors.