` и ``: по кускам такой текст * перевести грамотно нельзя, порядок слов в языках разный. */ final class BlocksController implements Hookable { public const NAMESPACE = 'lingocanvas/v1'; public const CAPABILITY = 'manage_options'; /** * @param SourceRepository $sources Исходные строки. * @param TranslationCache $cache Кэш переводов. * @param Settings $settings Настройки плагина. */ public function __construct( private readonly SourceRepository $sources, private readonly TranslationCache $cache, private readonly Settings $settings ) { } /** * {@inheritDoc} */ public function register(): void { add_action( 'rest_api_init', array( $this, 'registerRoutes' ) ); } /** * Регистрирует маршруты. */ public function registerRoutes(): void { register_rest_route( self::NAMESPACE, '/blocks', array( 'methods' => 'POST', 'callback' => array( $this, 'create' ), 'permission_callback' => array( $this, 'canEdit' ), 'args' => array( 'html' => array( 'required' => true, 'type' => 'string', 'sanitize_callback' => static fn( $value ): string => (string) wp_unslash( $value ), ), ), ) ); register_rest_route( self::NAMESPACE, '/blocks/(?P\d+)', array( 'methods' => 'DELETE', 'callback' => array( $this, 'delete' ), 'permission_callback' => array( $this, 'canEdit' ), 'args' => array( 'source_id' => array( 'required' => true, 'sanitize_callback' => 'absint', ), ), ) ); } /** * Право менять переводы. */ public function canEdit(): bool { return current_user_can( self::CAPABILITY ); } /** * Создаёт блок из разметки элемента. * * @param WP_REST_Request $request Запрос. * @return WP_REST_Response|WP_Error */ public function create( WP_REST_Request $request ) { $html = BlockSanitizer::sanitize( (string) $request->get_param( 'html' ) ); if ( ! Text::isTranslatable( wp_strip_all_tags( $html ) ) ) { return new WP_Error( 'lingocanvas_empty_block', __( 'There is nothing to translate in this fragment.', 'lingocanvas' ), array( 'status' => 400 ) ); } $locale = $this->settings->defaultLanguage()->locale; $sourceHash = Hash::of( $html ); $contextHash = Hash::of( '' ); $uniqHash = Hash::ofParts( array( $locale, Segment::KIND_HTML_BLOCK, $sourceHash, $contextHash, '', '', '' ) ); $this->sources->insertMissing( array( array( 'locale' => $locale, 'kind' => Segment::KIND_HTML_BLOCK, 'text' => $html, 'source_hash' => $sourceHash, 'context_hash' => $contextHash, 'uniq_hash' => $uniqHash, ), ) ); $ids = $this->sources->idsByHashes( array( $uniqHash ) ); if ( ! isset( $ids[ $uniqHash ] ) ) { return new WP_Error( 'lingocanvas_block_failed', __( 'Could not create the block.', 'lingocanvas' ), array( 'status' => 500 ) ); } // Список блоков влияет на разбор каждой страницы — сбрасываем его кэш. $this->sources->flushBlockHashes(); $this->cache->flush(); return new WP_REST_Response( array( 'id' => $ids[ $uniqHash ], 'kind' => Segment::KIND_HTML_BLOCK, 'source_text' => $html, ), 201 ); } /** * Удаляет блок вместе с его переводами. * * После удаления части абзаца снова переводятся по отдельности. * * @param WP_REST_Request $request Запрос. * @return WP_REST_Response|WP_Error */ public function delete( WP_REST_Request $request ) { $sourceId = (int) $request->get_param( 'source_id' ); $source = $this->sources->find( $sourceId ); if ( null === $source || Segment::KIND_HTML_BLOCK !== (string) $source['kind'] ) { return new WP_Error( 'lingocanvas_block_not_found', __( 'Block not found.', 'lingocanvas' ), array( 'status' => 404 ) ); } $this->sources->deleteWithTranslations( $sourceId ); $this->sources->flushBlockHashes(); $this->cache->flush(); return new WP_REST_Response( array( 'id' => $sourceId ) ); } }