/**
* Plugin Name: Polylang REST API Extension
* Description: Custom REST API endpoints for Polylang free version
* Version: 1.0.0
* Author: Your Name
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
class Polylang_REST_API_Extension {
private $namespace = 'polylang-api/v1';
public function __construct() {
add_action('rest_api_init', array($this, 'register_routes'));
}
/**
* Register all REST API routes
*/
public function register_routes() {
// Get untranslated posts
register_rest_route($this->namespace, '/untranslated-posts', array(
'methods' => 'GET',
'callback' => array($this, 'get_untranslated_posts'),
'permission_callback' => array($this, 'check_permission'),
'args' => array(
'post_type' => array(
'default' => 'post',
'sanitize_callback' => 'sanitize_text_field',
),
'source_lang' => array(
'default' => 'en',
'sanitize_callback' => 'sanitize_text_field',
),
'target_lang' => array(
'required' => true,
'sanitize_callback' => 'sanitize_text_field',
),
),
));
// Get post content for translation
register_rest_route($this->namespace, '/post/(?P<id>\d+)', array(
'methods' => 'GET',
'callback' => array($this, 'get_post_content'),
'permission_callback' => array($this, 'check_permission'),
'args' => array(
'id' => array(
'validate_callback' => function($param) {
return is_numeric($param);
}
),
),
));
// Create translated post
register_rest_route($this->namespace, '/translate-post', array(
'methods' => 'POST',
'callback' => array($this, 'create_translated_post'),
'permission_callback' => array($this, 'check_permission'),
));
// Get languages
register_rest_route($this->namespace, '/languages', array(
'methods' => 'GET',
'callback' => array($this, 'get_languages'),
'permission_callback' => array($this, 'check_permission'),
));
// Get translation status
register_rest_route($this->namespace, '/translation-status/(?P<id>\d+)', array(
'methods' => 'GET',
'callback' => array($this, 'get_translation_status'),
'permission_callback' => array($this, 'check_permission'),
));
}
/**
* Permission callback - check if user can edit posts
*/
public function check_permission() {
return current_user_can('edit_posts');
}
/**
* Get all untranslated posts for a specific language
*/
public function get_untranslated_posts($request) {
if (!function_exists('pll_languages_list')) {
return new WP_Error('polylang_not_active', 'Polylang is not active', array('status' => 500));
}
$post_type = $request->get_param('post_type');
$source_lang = $request->get_param('source_lang');
$target_lang = $request->get_param('target_lang');
// Get all posts in source language
$args = array(
'post_type' => $post_type,
'posts_per_page' => -1,
'lang' => $source_lang,
'post_status' => 'publish',
);
$posts = get_posts($args);
$untranslated = array();
foreach ($posts as $post) {
// Check if translation exists in target language
$translation_id = pll_get_post($post->ID, $target_lang);
if (!$translation_id) {
$untranslated[] = array(
'id' => $post->ID,
'title' => $post->post_title,
'slug' => $post->post_name,
'status' => $post->post_status,
'date' => $post->post_date,
'modified' => $post->post_modified,
'post_type' => $post->post_type,
'language' => $source_lang,
);
}
}
return rest_ensure_response(array(
'success' => true,
'count' => count($untranslated),
'posts' => $untranslated,
));
}
/**
* Get full post content for translation
*/
public function get_post_content($request) {
$post_id = $request->get_param('id');
$post = get_post($post_id);
if (!$post) {
return new WP_Error('post_not_found', 'Post not found', array('status' => 404));
}
// Get post meta
$post_meta = get_post_meta($post_id);
// Get categories and tags
$categories = wp_get_post_categories($post_id, array('fields' => 'all'));
$tags = wp_get_post_tags($post_id, array('fields' => 'all'));
// Get featured image
$thumbnail_id = get_post_thumbnail_id($post_id);
$thumbnail_url = $thumbnail_id ? wp_get_attachment_url($thumbnail_id) : null;
// Get ACF fields if ACF is active
$acf_fields = array();
if (function_exists('get_fields')) {
$acf_fields = get_fields($post_id);
}
// Get current language
$current_lang = pll_get_post_language($post_id);
return rest_ensure_response(array(
'success' => true,
'post' => array(
'id' => $post->ID,
'title' => $post->post_title,
'content' => $post->post_content,
'excerpt' => $post->post_excerpt,
'slug' => $post->post_name,
'status' => $post->post_status,
'type' => $post->post_type,
'language' => $current_lang,
'featured_image' => $thumbnail_url,
'thumbnail_id' => $thumbnail_id,
'categories' => $categories,
'tags' => $tags,
'meta' => $post_meta,
'acf_fields' => $acf_fields,
),
));
}
/**
* Create a translated version of a post
*/
public function create_translated_post($request) {
$params = $request->get_json_params();
if (!isset($params['source_post_id']) || !isset($params['target_lang'])) {
return new WP_Error('missing_params', 'Missing required parameters', array('status' => 400));
}
$source_post_id = intval($params['source_post_id']);
$target_lang = sanitize_text_field($params['target_lang']);
$translated_title = sanitize_text_field($params['title']);
$translated_content = wp_kses_post($params['content']);
$translated_excerpt = isset($params['excerpt']) ? sanitize_textarea_field($params['excerpt']) : '';
$source_post = get_post($source_post_id);
if (!$source_post) {
return new WP_Error('post_not_found', 'Source post not found', array('status' => 404));
}
// Check if translation already exists
$existing_translation = pll_get_post($source_post_id, $target_lang);
if ($existing_translation) {
// Update existing translation
$updated_post = array(
'ID' => $existing_translation,
'post_title' => $translated_title,
'post_content' => $translated_content,
'post_excerpt' => $translated_excerpt,
);
$result = wp_update_post($updated_post, true);
if (is_wp_error($result)) {
return $result;
}
$translated_post_id = $existing_translation;
$action = 'updated';
} else {
// Create new translation
$new_post = array(
'post_title' => $translated_title,
'post_content' => $translated_content,
'post_excerpt' => $translated_excerpt,
'post_status' => $source_post->post_status,
'post_type' => $source_post->post_type,
'post_author' => $source_post->post_author,
);
$translated_post_id = wp_insert_post($new_post, true);
if (is_wp_error($translated_post_id)) {
return $translated_post_id;
}
// Set language for the new post
pll_set_post_language($translated_post_id, $target_lang);
// Link the translation
pll_save_post_translations(array(
pll_get_post_language($source_post_id) => $source_post_id,
$target_lang => $translated_post_id,
));
$action = 'created';
}
// Copy featured image if exists
$thumbnail_id = get_post_thumbnail_id($source_post_id);
if ($thumbnail_id) {
set_post_thumbnail($translated_post_id, $thumbnail_id);
}
// Translate and assign categories
if (isset($params['categories']) && is_array($params['categories'])) {
$translated_category_ids = array();
foreach ($params['categories'] as $category_data) {
$cat_id = $this->translate_term($category_data, 'category', $target_lang);
if ($cat_id) {
$translated_category_ids[] = $cat_id;
}
}
wp_set_post_categories($translated_post_id, $translated_category_ids);
}
// Translate and assign tags
if (isset($params['tags']) && is_array($params['tags'])) {
$translated_tag_ids = array();
foreach ($params['tags'] as $tag_data) {
$tag_id = $this->translate_term($tag_data, 'post_tag', $target_lang);
if ($tag_id) {
$translated_tag_ids[] = $tag_id;
}
}
wp_set_post_tags($translated_post_id, $translated_tag_ids);
}
// Handle ACF fields if provided
if (isset($params['acf_fields']) && function_exists('update_field')) {
foreach ($params['acf_fields'] as $field_key => $field_value) {
update_field($field_key, $field_value, $translated_post_id);
}
}
return rest_ensure_response(array(
'success' => true,
'action' => $action,
'translated_post_id' => $translated_post_id,
'source_post_id' => $source_post_id,
'target_language' => $target_lang,
'url' => get_permalink($translated_post_id),
));
}
/**
* Helper function to translate taxonomy terms
*/
private function translate_term($term_data, $taxonomy, $target_lang) {
$source_term_id = isset($term_data['term_id']) ? intval($term_data['term_id']) : 0;
$translated_name = isset($term_data['translated_name']) ? sanitize_text_field($term_data['translated_name']) : '';
if ($source_term_id && function_exists('pll_get_term')) {
// Check if translation exists
$existing_translation = pll_get_term($source_term_id, $target_lang);
if ($existing_translation) {
return $existing_translation;
}
}
// Create new term if translation doesn't exist
if ($translated_name) {
$new_term = wp_insert_term($translated_name, $taxonomy);
if (!is_wp_error($new_term)) {
$new_term_id = $new_term['term_id'];
// Set language and link translation
if (function_exists('pll_set_term_language')) {
pll_set_term_language($new_term_id, $target_lang);
if ($source_term_id) {
$source_lang = pll_get_term_language($source_term_id);
pll_save_term_translations(array(
$source_lang => $source_term_id,
$target_lang => $new_term_id,
));
}
}
return $new_term_id;
}
}
return null;
}
/**
* Get all configured languages
*/
public function get_languages() {
if (!function_exists('pll_languages_list')) {
return new WP_Error('polylang_not_active', 'Polylang is not active', array('status' => 500));
}
$languages = pll_languages_list(array('fields' => 'all'));
$lang_data = array();
foreach ($languages as $lang) {
$lang_data[] = array(
'slug' => $lang->slug,
'name' => $lang->name,
'locale' => $lang->locale,
'is_rtl' => $lang->is_rtl,
'flag' => $lang->flag,
);
}
return rest_ensure_response(array(
'success' => true,
'languages' => $lang_data,
));
}
/**
* Get translation status for a specific post
*/
public function get_translation_status($request) {
$post_id = $request->get_param('id');
if (!function_exists('pll_get_post_translations')) {
return new WP_Error('polylang_not_active', 'Polylang is not active', array('status' => 500));
}
$translations = pll_get_post_translations($post_id);
$languages = pll_languages_list();
$status = array();
foreach ($languages as $lang) {
$status[$lang] = isset($translations[$lang]) ? array(
'exists' => true,
'post_id' => $translations[$lang],
'url' => get_permalink($translations[$lang]),
) : array(
'exists' => false,
'post_id' => null,
'url' => null,
);
}
return rest_ensure_response(array(
'success' => true,
'post_id' => $post_id,
'translations' => $status,
));
}
}
// Initialize the plugin
new Polylang_REST_API_Extension();