array( 'type' => 'string', 'required' => true, ), ); register_rest_route( self::NS, '/bulk/pending', array( 'methods' => WP_REST_Server::READABLE, 'callback' => $this->safely( array( $this, 'pending' ) ), 'permission_callback' => array( $this, 'can_manage' ), 'args' => $lang, ) ); register_rest_route( self::NS, '/bulk/post', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => $this->safely( array( $this, 'post' ) ), 'permission_callback' => array( $this, 'can_manage' ), 'args' => array_merge( $lang, array( 'id' => array( 'type' => 'integer', 'required' => true, ), ) ), ) ); register_rest_route( self::NS, '/bulk/term', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => $this->safely( array( $this, 'term' ) ), 'permission_callback' => array( $this, 'can_manage' ), 'args' => array_merge( $lang, array( 'id' => array( 'type' => 'integer', 'required' => true, ), 'mode' => array( 'type' => 'string', 'default' => 'machine', ), ) ), ) ); register_rest_route( self::NS, '/bulk/menu', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => $this->safely( array( $this, 'menu' ) ), 'permission_callback' => array( $this, 'can_manage' ), 'args' => array_merge( $lang, array( 'id' => array( 'type' => 'integer', 'required' => true, ), ) ), ) ); register_rest_route( self::NS, '/bulk/part', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => $this->safely( array( $this, 'part' ) ), 'permission_callback' => array( $this, 'can_manage' ), 'args' => array_merge( $lang, array( 'slug' => array( 'type' => 'string', 'required' => true, ), ) ), ) ); } /** * Published content of the default language with no version in $lang, * and header/footer parts with no copy in $lang. * * @param WP_REST_Request> $request */ public function pending( WP_REST_Request $request ): WP_REST_Response|WP_Error { $lang = (string) $request['lang']; $error = $this->check_language( $lang ); if ( $error ) { return $error; } require_once ABSPATH . 'wp-admin/includes/post.php'; // Defines use_block_editor_for_post outside wp-admin. $posts = array(); foreach ( $this->coverage->missing_posts( $lang, self::LIMIT ) as $post ) { $type = get_post_type_object( $post->post_type ); $posts[] = array( 'id' => $post->ID, 'title' => '' !== $post->post_title ? html_entity_decode( $post->post_title, ENT_QUOTES, 'UTF-8' ) : '#' . $post->ID, 'type' => $type ? (string) $type->labels->singular_name : $post->post_type, 'edit_link' => (string) get_edit_post_link( $post->ID, 'raw' ), // Block editor: translated in a hidden editor (blocks stay valid). Classic: on the server. 'editor' => use_block_editor_for_post( $post ) ? 'block' : 'classic', ); } $parts = array(); foreach ( $this->coverage->parts_status( $lang ) as $part ) { if ( ! $part['exists'] ) { $parts[] = array( 'slug' => $part['slug'], 'title' => $part['title'], ); } } $menus = array(); foreach ( $this->menus->status( $lang ) as $menu ) { if ( ! $menu['copy'] ) { $menus[] = array( 'id' => $menu['id'], 'title' => $menu['title'], ); } } return new WP_REST_Response( array( 'posts' => $posts, 'parts' => $parts, 'menus' => $menus, 'terms' => $this->missing_terms( $lang ), ) ); } /** * Machine-translated copy of one header/footer part. * * @param WP_REST_Request> $request */ public function part( WP_REST_Request $request ): WP_REST_Response|WP_Error { $lang = (string) $request['lang']; $error = $this->check_language( $lang ); if ( $error ) { return $error; } if ( function_exists( 'set_time_limit' ) ) { @set_time_limit( 300 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- may be disabled by the host. } $slug = sanitize_title( (string) $request['slug'] ); $result = $this->parts->create_translation( $slug, $lang, 'machine' ); if ( $result instanceof WP_Error ) { return self::fail( $result, 'blocklingo_exists' === $result->get_error_code() ? 409 : 502 ); } return new WP_REST_Response( array( 'id' => $result, 'slug' => TemplateParts::slug_for( $slug, $lang ), 'edit_link' => TemplateParts::edit_link( TemplateParts::slug_for( $slug, $lang ) ), ) ); } /** * Terms of the default language with no version in $lang, parents first. * * @return array */ private function missing_terms( string $lang ): array { $taxonomies = $this->terms->taxonomies(); if ( ! $taxonomies ) { return array(); } $default = $this->settings->get()->default_code(); $all = get_terms( array( 'taxonomy' => $taxonomies, 'hide_empty' => false, 'number' => self::LIMIT, 'orderby' => 'parent', ) ); $out = array(); foreach ( is_array( $all ) ? $all : array() as $term ) { if ( $this->terms->language_of( $term->term_id ) !== $default || $this->terms->in_language( $term->term_id, $lang ) ) { continue; } if ( 'uncategorized' === $term->slug ) { continue; // The fallback category stays shared. } $tax = get_taxonomy( $term->taxonomy ); $out[] = array( 'id' => $term->term_id, 'title' => html_entity_decode( $term->name, ENT_QUOTES, 'UTF-8' ), 'type' => $tax ? (string) $tax->labels->singular_name : $term->taxonomy, ); } return $out; } /** * Machine-translated draft of one post that does not use the block editor * (classic editor, title and fields only): translated on the server. * * @param WP_REST_Request> $request */ public function post( WP_REST_Request $request ): WP_REST_Response|WP_Error { $lang = (string) $request['lang']; $error = $this->check_language( $lang ); if ( $error ) { return $error; } $id = (int) $request['id']; $post = get_post( $id ); if ( ! $post || ! $this->settings->get()->is_translatable_type( $post->post_type ) ) { return new WP_Error( 'blocklingo_no_post', __( 'Post not found.', 'blocklingo' ), array( 'status' => 404 ) ); } if ( function_exists( 'set_time_limit' ) ) { @set_time_limit( 300 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- may be disabled by the host. } $result = $this->duplicator->create( $id, $lang, Duplicator::MODE_MACHINE ); if ( $result instanceof WP_Error ) { return self::fail( $result, 'blocklingo_exists' === $result->get_error_code() ? 409 : 502 ); } return new WP_REST_Response( array( 'id' => $result, 'edit_link' => (string) get_edit_post_link( $result, 'raw' ), ) ); } /** * Machine-translated version of one term (mode "copy" for tests and manual use). * * @param WP_REST_Request> $request */ public function term( WP_REST_Request $request ): WP_REST_Response|WP_Error { $lang = (string) $request['lang']; $error = $this->check_language( $lang ); if ( $error ) { return $error; } $mode = 'copy' === $request['mode'] ? 'copy' : 'machine'; $result = $this->terms->create_translation( (int) $request['id'], $lang, $mode ); if ( $result instanceof WP_Error ) { return self::fail( $result, 'blocklingo_exists' === $result->get_error_code() ? 409 : 502 ); } return new WP_REST_Response( array( 'id' => $result, 'edit_link' => (string) get_edit_term_link( $result ), ) ); } /** * Machine-translated, published copy of one navigation menu. * * @param WP_REST_Request> $request */ public function menu( WP_REST_Request $request ): WP_REST_Response|WP_Error { $lang = (string) $request['lang']; $error = $this->check_language( $lang ); if ( $error ) { return $error; } $result = $this->menus->create_translation( (int) $request['id'], $lang, 'machine' ); if ( $result instanceof WP_Error ) { return self::fail( $result, 'blocklingo_exists' === $result->get_error_code() ? 409 : 502 ); } return new WP_REST_Response( array( 'id' => $result, 'edit_link' => Menus::edit_link( $result ), ) ); } private function check_language( string $lang ): ?WP_Error { $settings = $this->settings->get(); if ( ! $settings->is_language( $lang ) || $settings->default_code() === $lang ) { return new WP_Error( 'blocklingo_bad_lang', __( 'Pick a language other than the default one.', 'blocklingo' ), array( 'status' => 400 ) ); } return null; } }