403Webshell
Server IP : 121.121.20.254  /  Your IP : 216.73.216.202
Web Server : Microsoft-IIS/10.0
System : Windows NT WEB-SERVER 10.0 build 20348 (Windows Server 2022) AMD64
User : IUSR ( 0)
PHP Version : 8.3.28
Disable Function : NONE
MySQL : ON  |  cURL : ON  |  WGET : OFF  |  Perl : OFF  |  Python : OFF  |  Sudo : OFF  |  Pkexec : OFF
Directory :  C:/inetpub/wwwroot/AIWEBSTATION/wp-content/plugins/ai-engine-pro/premium/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : C:/inetpub/wwwroot/AIWEBSTATION/wp-content/plugins/ai-engine-pro/premium/mcp-wpml.php
<?php

// WPML counterpart of mcp-polylang.php. WPML has no pll_*-style functions: everything
// goes through its hooks API (wpml_active_languages, wpml_element_trid,
// wpml_set_element_language_details, wpml_object_id, wpml_switch_language), and for
// terms the element IDs are term_taxonomy_ids, not term_ids. Tool outputs mirror the
// Polylang tools so agents get the same shapes on either plugin.
class MeowPro_MWAI_MCP_Wpml {
  private $core = null;

  #region Initialize
  public function __construct( $core ) {
    $this->core = $core;
    add_action( 'rest_api_init', [ $this, 'rest_api_init' ] );
  }

  public function rest_api_init() {
    add_filter( 'mwai_mcp_tools', [ $this, 'register_rest_tools' ] );
    add_filter( 'mwai_mcp_callback', [ $this, 'handle_call' ], 10, 4 );
  }
  #endregion

  #region Helpers

  private function empty_schema(): array {
    return [ 'type' => 'object', 'properties' => (object) [] ];
  }

  private function get_wpml_languages(): array {
    $default = apply_filters( 'wpml_default_language', null );
    $active = apply_filters( 'wpml_active_languages', null, [ 'skip_missing' => 0 ] );
    $languages = [];
    foreach ( (array) $active as $lang ) {
      $languages[] = [
        'slug' => $lang['code'],
        'name' => $lang['native_name'],
        'locale' => $lang['default_locale'],
        'is_default' => $lang['code'] === $default,
        'flag_url' => $lang['country_flag_url'] ?? null,
      ];
    }
    return $languages;
  }

  private function validate_language( string $lang ): bool {
    foreach ( $this->get_wpml_languages() as $l ) {
      if ( $l['slug'] === $lang ) {
        return true;
      }
    }
    return false;
  }

  private function get_post_type_label( string $post_type ): string {
    $obj = get_post_type_object( $post_type );
    return $obj ? $obj->labels->singular_name : $post_type;
  }

  private function get_post_language( int $post_id ): ?string {
    $details = apply_filters( 'wpml_post_language_details', null, $post_id );
    if ( is_wp_error( $details ) || !is_array( $details ) || empty( $details['language_code'] ) ) {
      return null;
    }
    return $details['language_code'];
  }

  private function post_element_type( int $post_id ): string {
    return 'post_' . get_post_type( $post_id );
  }

  // Runs a callback with WPML switched to another language ('all' works too),
  // always restoring the previous language.
  private function with_language( string $lang, callable $fn ) {
    $current = apply_filters( 'wpml_current_language', null );
    do_action( 'wpml_switch_language', $lang );
    try {
      return $fn();
    }
    finally {
      do_action( 'wpml_switch_language', $current );
    }
  }

  // Translations of a post, as [ lang => post_id ], mirroring pll_get_post_translations.
  private function get_post_translations_map( int $post_id ): array {
    $el_type = $this->post_element_type( $post_id );
    $trid = apply_filters( 'wpml_element_trid', null, $post_id, $el_type );
    if ( !$trid ) {
      return [];
    }
    $translations = apply_filters( 'wpml_get_element_translations', null, $trid, $el_type );
    $map = [];
    foreach ( (array) $translations as $lang => $t ) {
      if ( !empty( $t->element_id ) ) {
        $map[ $lang ] = (int) $t->element_id;
      }
    }
    return $map;
  }

  #endregion

  #region Tools
  private function tools(): array {
    return [

      /* ───────── Languages ───────── */
      'wpml_get_languages' => [
        'name' => 'wpml_get_languages',
        'description' => 'List all configured languages in WPML (slug, name, locale, is_default, flag_url).',
        'inputSchema' => $this->empty_schema(),
        'accessLevel' => 'read',
      ],

      /* ───────── Post Language ───────── */
      'wpml_get_post_language' => [
        'name' => 'wpml_get_post_language',
        'description' => 'Get the language of a specific post.',
        'inputSchema' => [
          'type' => 'object',
          'properties' => [
            'post_id' => [ 'type' => 'integer', 'description' => 'The post ID' ],
          ],
          'required' => [ 'post_id' ],
        ],
        'accessLevel' => 'read',
      ],

      'wpml_set_post_language' => [
        'name' => 'wpml_set_post_language',
        'description' => 'Set the language of a post.',
        'inputSchema' => [
          'type' => 'object',
          'properties' => [
            'post_id' => [ 'type' => 'integer', 'description' => 'The post ID' ],
            'lang' => [ 'type' => 'string', 'description' => 'Language slug (e.g., "en", "fr", "ja")' ],
          ],
          'required' => [ 'post_id', 'lang' ],
        ],
        'accessLevel' => 'write',
      ],

      /* ───────── Post Translations ───────── */
      'wpml_get_post_translations' => [
        'name' => 'wpml_get_post_translations',
        'description' => 'Get all translations of a post. Returns an object mapping language slugs to post IDs.',
        'inputSchema' => [
          'type' => 'object',
          'properties' => [
            'post_id' => [ 'type' => 'integer', 'description' => 'The post ID' ],
          ],
          'required' => [ 'post_id' ],
        ],
        'accessLevel' => 'read',
      ],

      'wpml_link_translations' => [
        'name' => 'wpml_link_translations',
        'description' => 'Link multiple posts as translations of each other. All posts must have a language set and share the same post type.',
        'inputSchema' => [
          'type' => 'object',
          'properties' => [
            'translations' => [
              'type' => 'object',
              'description' => 'Object mapping language slugs to post IDs, e.g., {"en": 123, "fr": 456}',
              'additionalProperties' => [ 'type' => 'integer' ],
            ],
          ],
          'required' => [ 'translations' ],
        ],
        'accessLevel' => 'write',
      ],

      /* ───────── Term Translations ───────── */
      'wpml_get_term_translations' => [
        'name' => 'wpml_get_term_translations',
        'description' => 'Get all translations of a term. Returns an object mapping language slugs to term IDs.',
        'inputSchema' => [
          'type' => 'object',
          'properties' => [
            'term_id' => [ 'type' => 'integer', 'description' => 'The term ID' ],
          ],
          'required' => [ 'term_id' ],
        ],
        'accessLevel' => 'read',
      ],

      'wpml_translate_term' => [
        'name' => 'wpml_translate_term',
        'description' => 'Get a specific translation of a term. Returns the term ID in the target language, or null if not available.',
        'inputSchema' => [
          'type' => 'object',
          'properties' => [
            'term_id' => [ 'type' => 'integer', 'description' => 'The term ID' ],
            'lang' => [ 'type' => 'string', 'description' => 'Target language slug' ],
          ],
          'required' => [ 'term_id', 'lang' ],
        ],
        'accessLevel' => 'read',
      ],

      /* ───────── Query Posts by Language ───────── */
      'wpml_get_posts' => [
        'name' => 'wpml_get_posts',
        'description' => 'Get posts filtered by language. Wraps wp_get_posts with WPML language support.',
        'inputSchema' => [
          'type' => 'object',
          'properties' => [
            'lang' => [ 'type' => 'string', 'description' => 'Language slug (e.g., "en", "ja"). Use empty string "" to get posts in all languages.' ],
            'post_type' => [ 'type' => 'string', 'description' => 'Post type (default: post)' ],
            'post_status' => [ 'type' => 'string', 'description' => 'Post status (default: publish)' ],
            'limit' => [ 'type' => 'integer', 'description' => 'Maximum number of results (default: 10)' ],
            'offset' => [ 'type' => 'integer', 'description' => 'Number of posts to skip (default: 0)' ],
            'orderby' => [ 'type' => 'string', 'description' => 'Order by field (default: date)' ],
            'order' => [ 'type' => 'string', 'description' => 'Order direction: ASC or DESC (default: DESC)' ],
          ],
          'required' => [ 'lang' ],
        ],
        'accessLevel' => 'read',
      ],

      /* ───────── Missing Translations ───────── */
      'wpml_get_posts_missing_translation' => [
        'name' => 'wpml_get_posts_missing_translation',
        'description' => 'Find posts that are missing a translation in a specific language.',
        'inputSchema' => [
          'type' => 'object',
          'properties' => [
            'source_lang' => [ 'type' => 'string', 'description' => 'Source language slug to search in' ],
            'target_lang' => [ 'type' => 'string', 'description' => 'Target language slug that is missing' ],
            'post_type' => [ 'type' => 'string', 'description' => 'Post type to filter (default: post)' ],
            'limit' => [ 'type' => 'integer', 'description' => 'Maximum number of results (default: 20)' ],
          ],
          'required' => [ 'source_lang', 'target_lang' ],
        ],
        'accessLevel' => 'read',
      ],

      /* ───────── Create Translation ───────── */
      'wpml_create_translation' => [
        'name' => 'wpml_create_translation',
        'description' => 'Create a new post as a translation of an existing post. Creates the post, sets its language, and links it to the source. Can optionally translate content, categories, and tags.',
        'inputSchema' => [
          'type' => 'object',
          'properties' => [
            'source_post_id' => [ 'type' => 'integer', 'description' => 'The source post ID to translate from' ],
            'target_lang' => [ 'type' => 'string', 'description' => 'Target language slug for the new translation' ],
            'title' => [ 'type' => 'string', 'description' => 'Translated title' ],
            'content' => [ 'type' => 'string', 'description' => 'Translated content (optional, copies from source if not provided)' ],
            'excerpt' => [ 'type' => 'string', 'description' => 'Translated excerpt (optional)' ],
            'status' => [ 'type' => 'string', 'description' => 'Post status: draft, publish, pending, private, future (default: draft)' ],
            'post_date' => [ 'type' => 'string', 'description' => 'Post date in ISO 8601 or MySQL format (required for future status, e.g., "2025-12-31 10:00:00")' ],
            'translate_terms' => [ 'type' => 'boolean', 'description' => 'Automatically link translated categories/tags if they exist (default: true)' ],
            'copy_featured_image' => [ 'type' => 'boolean', 'description' => 'Copy the featured image to the new translation (default: true)' ],
          ],
          'required' => [ 'source_post_id', 'target_lang', 'title' ],
        ],
        'accessLevel' => 'write',
      ],

      /* ───────── Translation Status ───────── */
      'wpml_translation_status' => [
        'name' => 'wpml_translation_status',
        'description' => 'Get an overview of translation coverage. Shows how many posts exist in each language and the percentage translated.',
        'inputSchema' => [
          'type' => 'object',
          'properties' => [
            'post_type' => [ 'type' => 'string', 'description' => 'Post type to analyze (default: post)' ],
          ],
        ],
        'accessLevel' => 'read',
      ],
    ];
  }
  #endregion

  #region Register
  public function register_rest_tools( array $prev ): array {
    $tools = $this->tools();

    // Add category and annotations to each tool
    foreach ( $tools as &$tool ) {
      if ( !isset( $tool['category'] ) ) {
        $tool['category'] = 'AI Engine (WPML)';
      }

      // Add MCP tool annotations
      if ( !isset( $tool['annotations'] ) ) {
        $name = $tool['name'];

        // Read-only tools
        $is_readonly = (
          $name === 'wpml_get_languages' ||
          $name === 'wpml_get_posts' ||
          $name === 'wpml_translation_status' ||
          strpos( $name, '_get_' ) !== false
        );

        // Destructive tools (none in WPML integration)
        $is_destructive = false;

        $tool['annotations'] = [
          'readOnlyHint' => $is_readonly,
          'destructiveHint' => $is_destructive,
          'openWorldHint' => false,
        ];
      }
    }

    $merged = array_merge( $prev, array_values( $tools ) );
    return $merged;
  }
  #endregion

  #region Callback
  public function handle_call( $prev, string $tool, array $args, ?int $id ) {
    if ( !empty( $prev ) || !isset( $this->tools()[ $tool ] ) ) {
      return $prev; // handled elsewhere or unknown
    }
    if ( !current_user_can( 'administrator' ) ) {
      wp_set_current_user( 1 );
    }
    return $this->dispatch( $tool, $args, $id );
  }
  #endregion

  #region Dispatcher
  private function dispatch( string $tool, array $a, ?int $id ) {

    // Check if WPML is available
    if ( !defined( 'ICL_SITEPRESS_VERSION' ) ) {
      throw new Exception( 'WPML is not active' );
    }

    switch ( $tool ) {

      /* ───────── Languages ───────── */
      case 'wpml_get_languages':
        return $this->get_wpml_languages();

        /* ───────── Post Language ───────── */
      case 'wpml_get_post_language':
        $post_id = (int) ( $a['post_id'] ?? 0 );
        if ( !$post_id || !get_post( $post_id ) ) {
          throw new Exception( 'Invalid post ID' );
        }
        $lang = $this->get_post_language( $post_id );
        if ( !$lang ) {
          return [ 'post_id' => $post_id, 'language' => null, 'message' => 'No language set' ];
        }
        return [ 'post_id' => $post_id, 'language' => $lang ];

      case 'wpml_set_post_language':
        $post_id = (int) ( $a['post_id'] ?? 0 );
        $lang = sanitize_text_field( $a['lang'] ?? '' );
        if ( !$post_id || !get_post( $post_id ) ) {
          throw new Exception( 'Invalid post ID' );
        }
        if ( !$lang || !$this->validate_language( $lang ) ) {
          throw new Exception( 'Invalid language slug' );
        }
        $el_type = $this->post_element_type( $post_id );
        // Keep the existing trid so changing the language never unlinks translations.
        $trid = apply_filters( 'wpml_element_trid', null, $post_id, $el_type );
        do_action( 'wpml_set_element_language_details', [
          'element_id' => $post_id,
          'element_type' => $el_type,
          'trid' => $trid,
          'language_code' => $lang,
          'source_language_code' => null,
        ] );
        return [ 'post_id' => $post_id, 'language' => $lang, 'message' => 'Language set successfully' ];

        /* ───────── Post Translations ───────── */
      case 'wpml_get_post_translations':
        $post_id = (int) ( $a['post_id'] ?? 0 );
        if ( !$post_id || !get_post( $post_id ) ) {
          throw new Exception( 'Invalid post ID' );
        }
        $translations = $this->get_post_translations_map( $post_id );
        $result = [];
        foreach ( $translations as $lang => $translated_post_id ) {
          $post = get_post( $translated_post_id );
          $result[ $lang ] = [
            'post_id' => $translated_post_id,
            'title' => $post ? $post->post_title : null,
            'status' => $post ? $post->post_status : null,
          ];
        }
        return [ 'source_post_id' => $post_id, 'translations' => $result ];

      case 'wpml_link_translations':
        $translations = $a['translations'] ?? [];
        // Handle JSON strings (some MCP clients send objects as JSON strings)
        if ( is_string( $translations ) ) {
          $translations = json_decode( $translations, true ) ?? [];
        }
        if ( empty( $translations ) || !is_array( $translations ) ) {
          throw new Exception( 'translations must be an object mapping language slugs to post IDs' );
        }
        // Validate all posts and languages; they must share one post type since the
        // WPML translation group (trid) is scoped to a single element type.
        $post_types = [];
        foreach ( $translations as $lang => $post_id ) {
          if ( !$this->validate_language( $lang ) ) {
            throw new Exception( "Invalid language: $lang" );
          }
          if ( !get_post( $post_id ) ) {
            throw new Exception( "Invalid post ID: $post_id" );
          }
          $post_lang = $this->get_post_language( (int) $post_id );
          if ( $post_lang !== $lang ) {
            throw new Exception( "Post $post_id has language '$post_lang', expected '$lang'" );
          }
          $post_types[ get_post_type( $post_id ) ] = true;
        }
        if ( count( $post_types ) > 1 ) {
          throw new Exception( 'All posts must share the same post type' );
        }
        // Anchor the group on the default language when present, else the first entry.
        $default = apply_filters( 'wpml_default_language', null );
        $source_lang = isset( $translations[ $default ] ) ? $default : array_key_first( $translations );
        $source_id = (int) $translations[ $source_lang ];
        $el_type = $this->post_element_type( $source_id );
        $trid = apply_filters( 'wpml_element_trid', null, $source_id, $el_type );
        foreach ( $translations as $lang => $post_id ) {
          if ( (int) $post_id === $source_id ) {
            continue;
          }
          do_action( 'wpml_set_element_language_details', [
            'element_id' => (int) $post_id,
            'element_type' => $el_type,
            'trid' => $trid,
            'language_code' => $lang,
            'source_language_code' => $source_lang,
          ] );
        }
        return [ 'linked' => $translations, 'message' => 'Posts linked as translations' ];

        /* ───────── Term Translations ───────── */
      case 'wpml_get_term_translations':
        $term_id = (int) ( $a['term_id'] ?? 0 );
        $term = $term_id ? get_term( $term_id ) : null;
        if ( !$term || is_wp_error( $term ) ) {
          throw new Exception( 'Invalid term ID' );
        }
        // WPML tracks taxonomy elements by term_taxonomy_id, not term_id.
        $el_type = 'tax_' . $term->taxonomy;
        $trid = apply_filters( 'wpml_element_trid', null, $term->term_taxonomy_id, $el_type );
        $translations = $trid ?
          apply_filters( 'wpml_get_element_translations', null, $trid, $el_type ) : [];
        $result = [];
        foreach ( (array) $translations as $lang => $t ) {
          if ( empty( $t->element_id ) ) {
            continue;
          }
          $translated = get_term_by( 'term_taxonomy_id', (int) $t->element_id, $term->taxonomy );
          if ( $translated ) {
            $result[ $lang ] = [
              'term_id' => $translated->term_id,
              'name' => $translated->name,
              'taxonomy' => $translated->taxonomy,
            ];
          }
        }
        return [ 'source_term_id' => $term_id, 'translations' => $result ];

      case 'wpml_translate_term':
        $term_id = (int) ( $a['term_id'] ?? 0 );
        $lang = sanitize_text_field( $a['lang'] ?? '' );
        $term = $term_id ? get_term( $term_id ) : null;
        if ( !$term || is_wp_error( $term ) ) {
          throw new Exception( 'Invalid term ID' );
        }
        if ( !$lang || !$this->validate_language( $lang ) ) {
          throw new Exception( 'Invalid language slug' );
        }
        $translated_id = apply_filters( 'wpml_object_id', $term_id, $term->taxonomy, false, $lang );
        if ( !$translated_id ) {
          return [ 'term_id' => $term_id, 'target_lang' => $lang, 'translated_term_id' => null, 'message' => 'No translation found' ];
        }
        $translated = get_term( $translated_id );
        return [
          'term_id' => $term_id,
          'target_lang' => $lang,
          'translated_term_id' => (int) $translated_id,
          'name' => ( $translated && !is_wp_error( $translated ) ) ? $translated->name : null,
        ];

        /* ───────── Query Posts by Language ───────── */
      case 'wpml_get_posts':
        $lang = $a['lang'] ?? null;
        $post_type = sanitize_text_field( $a['post_type'] ?? 'post' );
        $post_status = sanitize_text_field( $a['post_status'] ?? 'publish' );
        $limit = (int) ( $a['limit'] ?? 10 );
        $limit = max( 1, min( 100, $limit ) );
        $offset = (int) ( $a['offset'] ?? 0 );
        $orderby = sanitize_text_field( $a['orderby'] ?? 'date' );
        $order = strtoupper( sanitize_text_field( $a['order'] ?? 'DESC' ) );
        $order = in_array( $order, [ 'ASC', 'DESC' ], true ) ? $order : 'DESC';

        // Validate language if provided (empty string means all languages)
        if ( $lang !== '' && $lang !== null && !$this->validate_language( $lang ) ) {
          throw new Exception( 'Invalid language slug' );
        }

        $query_args = [
          'post_type' => $post_type,
          'post_status' => $post_status,
          'posts_per_page' => $limit,
          'offset' => $offset,
          'orderby' => $orderby,
          'order' => $order,
          'suppress_filters' => false, // Required for WPML's language filter to work
        ];

        // WPML filters queries by the current language, so run the query switched.
        $switch_to = ( $lang === '' || $lang === null ) ? 'all' : $lang;
        $posts = $this->with_language( $switch_to, function () use ( $query_args ) {
          return get_posts( $query_args );
        } );

        $result = [];
        foreach ( $posts as $post ) {
          $result[] = [
            'ID' => $post->ID,
            'post_title' => $post->post_title,
            'post_type' => $post->post_type,
            'post_status' => $post->post_status,
            'post_date' => $post->post_date,
            'language' => $this->get_post_language( $post->ID ),
            'permalink' => get_permalink( $post->ID ),
          ];
        }

        return [
          'lang' => $lang,
          'post_type' => $post_type,
          'count' => count( $result ),
          'posts' => $result,
        ];

        /* ───────── Missing Translations ───────── */
      case 'wpml_get_posts_missing_translation':
        $source_lang = sanitize_text_field( $a['source_lang'] ?? '' );
        $target_lang = sanitize_text_field( $a['target_lang'] ?? '' );
        $post_type = sanitize_text_field( $a['post_type'] ?? 'post' );
        $limit = (int) ( $a['limit'] ?? 20 );
        $limit = max( 1, min( 100, $limit ) );

        if ( !$source_lang || !$this->validate_language( $source_lang ) ) {
          throw new Exception( 'Invalid source language' );
        }
        if ( !$target_lang || !$this->validate_language( $target_lang ) ) {
          throw new Exception( 'Invalid target language' );
        }
        if ( $source_lang === $target_lang ) {
          throw new Exception( 'Source and target languages must be different' );
        }

        // Get posts in source language
        $post_ids = $this->with_language( $source_lang, function () use ( $post_type ) {
          return get_posts( [
            'post_type' => $post_type,
            'post_status' => 'any',
            'posts_per_page' => -1,
            'fields' => 'ids',
            'suppress_filters' => false,
          ] );
        } );

        $missing = [];
        foreach ( $post_ids as $post_id ) {
          $translation = apply_filters( 'wpml_object_id', $post_id, $post_type, false, $target_lang );
          if ( !$translation ) {
            $post = get_post( $post_id );
            $missing[] = [
              'post_id' => $post_id,
              'title' => $post->post_title,
              'status' => $post->post_status,
              'post_type' => $post->post_type,
            ];
            if ( count( $missing ) >= $limit ) {
              break;
            }
          }
        }

        return [
          'source_lang' => $source_lang,
          'target_lang' => $target_lang,
          'post_type' => $post_type,
          'total_in_source' => count( $post_ids ),
          'missing_count' => count( $missing ),
          'posts' => $missing,
        ];

        /* ───────── Create Translation ───────── */
      case 'wpml_create_translation':
        $source_post_id = (int) ( $a['source_post_id'] ?? 0 );
        $target_lang = sanitize_text_field( $a['target_lang'] ?? '' );
        $title = sanitize_text_field( $a['title'] ?? '' );
        $content = $a['content'] ?? null;
        $excerpt = $a['excerpt'] ?? null;
        $status = sanitize_text_field( $a['status'] ?? 'draft' );
        $post_date = isset( $a['post_date'] ) ? sanitize_text_field( $a['post_date'] ) : null;
        $translate_terms = $a['translate_terms'] ?? true;
        $copy_featured_image = $a['copy_featured_image'] ?? true;

        // Validate source post
        $source_post = get_post( $source_post_id );
        if ( !$source_post ) {
          throw new Exception( 'Invalid source post ID' );
        }

        // Validate target language
        if ( !$target_lang || !$this->validate_language( $target_lang ) ) {
          throw new Exception( 'Invalid target language' );
        }

        // Check if translation already exists
        $existing = apply_filters( 'wpml_object_id', $source_post_id, $source_post->post_type, false, $target_lang );
        if ( $existing && (int) $existing !== $source_post_id ) {
          throw new Exception( "Translation already exists in $target_lang (post ID: $existing)" );
        }

        // Get source language
        $source_lang = $this->get_post_language( $source_post_id );
        if ( !$source_lang ) {
          throw new Exception( 'Source post has no language set' );
        }
        if ( $source_lang === $target_lang ) {
          throw new Exception( 'Source and target languages must be different' );
        }

        // Validate title
        if ( empty( $title ) ) {
          throw new Exception( 'Title is required' );
        }

        // Validate status
        $allowed_statuses = [ 'draft', 'publish', 'pending', 'private', 'future' ];
        if ( !in_array( $status, $allowed_statuses, true ) ) {
          $status = 'draft';
        }

        // Validate future status requires post_date
        if ( $status === 'future' && empty( $post_date ) ) {
          throw new Exception( 'post_date is required when status is "future"' );
        }

        // Prepare post data
        $post_data = [
          'post_type' => $source_post->post_type,
          'post_title' => $title,
          'post_content' => $content !== null ? $content : $source_post->post_content,
          'post_excerpt' => $excerpt !== null ? $excerpt : $source_post->post_excerpt,
          'post_status' => $status,
          'post_author' => $source_post->post_author,
        ];

        // Handle scheduled posts
        if ( $post_date ) {
          $post_data['post_date'] = $post_date;
          $post_data['post_date_gmt'] = get_gmt_from_date( $post_date );
        }

        // Create the new post switched to the target language, so WPML's own
        // save-time hooks see a consistent language context.
        $new_post_id = $this->with_language( $target_lang, function () use ( $post_data ) {
          return wp_insert_post( $post_data, true );
        } );
        if ( is_wp_error( $new_post_id ) ) {
          throw new Exception( 'Failed to create post: ' . $new_post_id->get_error_message() );
        }

        // Set language and link to the source's translation group (same trid)
        $el_type = $this->post_element_type( $source_post_id );
        $trid = apply_filters( 'wpml_element_trid', null, $source_post_id, $el_type );
        do_action( 'wpml_set_element_language_details', [
          'element_id' => $new_post_id,
          'element_type' => $el_type,
          'trid' => $trid,
          'language_code' => $target_lang,
          'source_language_code' => $source_lang,
        ] );

        // Translate terms if requested
        $terms_linked = [];
        if ( $translate_terms ) {
          $taxonomies = get_object_taxonomies( $source_post->post_type );
          foreach ( $taxonomies as $taxonomy ) {
            $is_translated = apply_filters( 'wpml_is_translated_taxonomy', null, $taxonomy );
            if ( !$is_translated ) {
              continue;
            }
            $terms = wp_get_object_terms( $source_post_id, $taxonomy, [ 'fields' => 'ids' ] );
            $translated_terms = [];
            foreach ( $terms as $term_id ) {
              $translated_term = apply_filters( 'wpml_object_id', $term_id, $taxonomy, false, $target_lang );
              if ( $translated_term ) {
                $translated_terms[] = (int) $translated_term;
              }
            }
            if ( !empty( $translated_terms ) ) {
              $this->with_language( $target_lang, function () use ( $new_post_id, $translated_terms, $taxonomy ) {
                wp_set_object_terms( $new_post_id, $translated_terms, $taxonomy );
              } );
              $terms_linked[ $taxonomy ] = count( $translated_terms );
            }
          }
        }

        // Copy featured image if requested
        $featured_image_copied = false;
        if ( $copy_featured_image ) {
          $thumbnail_id = get_post_thumbnail_id( $source_post_id );
          if ( $thumbnail_id ) {
            set_post_thumbnail( $new_post_id, $thumbnail_id );
            $featured_image_copied = true;
          }
        }

        $result = [
          'new_post_id' => $new_post_id,
          'source_post_id' => $source_post_id,
          'source_lang' => $source_lang,
          'target_lang' => $target_lang,
          'title' => $title,
          'status' => $status,
          'terms_linked' => $terms_linked,
          'featured_image_copied' => $featured_image_copied,
          'message' => 'Translation created successfully',
        ];
        if ( $post_date ) {
          $result['post_date'] = $post_date;
        }
        return $result;

        /* ───────── Translation Status ───────── */
      case 'wpml_translation_status':
        $post_type = sanitize_text_field( $a['post_type'] ?? 'post' );
        $languages = $this->get_wpml_languages();
        $post_type_label = $this->get_post_type_label( $post_type );

        $stats = [];
        $total_posts = 0;
        $default_lang = null;

        // Count posts per language
        foreach ( $languages as $lang ) {
          $count = count( $this->with_language( $lang['slug'], function () use ( $post_type ) {
            return get_posts( [
              'post_type' => $post_type,
              'post_status' => 'any',
              'posts_per_page' => -1,
              'fields' => 'ids',
              'suppress_filters' => false,
            ] );
          } ) );
          $stats[ $lang['slug'] ] = [
            'name' => $lang['name'],
            'count' => $count,
          ];
          $total_posts += $count;
          if ( $lang['is_default'] ) {
            $default_lang = $lang['slug'];
          }
        }

        // Calculate translation coverage
        $default_count = $stats[ $default_lang ]['count'] ?? 0;
        foreach ( $stats as $lang => &$data ) {
          if ( $lang === $default_lang ) {
            $data['coverage'] = '100%';
          }
          elseif ( $default_count > 0 ) {
            $percentage = round( ( $data['count'] / $default_count ) * 100, 1 );
            $data['coverage'] = $percentage . '%';
          }
          else {
            $data['coverage'] = 'N/A';
          }
        }

        return [
          'post_type' => $post_type,
          'post_type_label' => $post_type_label,
          'default_language' => $default_lang,
          'total_posts' => $total_posts,
          'languages' => $stats,
        ];

      default:
        throw new Exception( 'Unknown tool' );
    }
  }
  #endregion
}

Youez - 2016 - github.com/yon3zu
LinuXploit