> $segments Сырые строки из тела запроса. * @param array $knownSources uniq_hash => строка sources, уже найденная в БД. * @param callable(int $sourceId): bool $belongsToPost Проверка «эта строка действительно из этой записи». * @param int $maxSegments Верхняя граница размера пакета. * @return array{ok: bool, errors: list, rows: list} */ public static function validate( array $segments, array $knownSources, callable $belongsToPost, int $maxSegments = 400 ): array { if ( array() === $segments ) { return array( 'ok' => false, 'errors' => array( self::ERROR_EMPTY ), 'rows' => array() ); } if ( count( $segments ) > $maxSegments ) { return array( 'ok' => false, 'errors' => array( self::ERROR_TOO_MANY ), 'rows' => array() ); } $errors = array(); $rows = array(); $seen = array(); foreach ( $segments as $segment ) { $hash = is_array( $segment ) ? (string) ( $segment['uniq_hash'] ?? '' ) : ''; if ( ! Hash::isValid( $hash ) ) { $errors[] = self::ERROR_INVALID_ID; continue; } if ( isset( $seen[ $hash ] ) ) { $errors[] = self::ERROR_DUPLICATE . ':' . $hash; continue; } $seen[ $hash ] = true; $source = $knownSources[ $hash ] ?? null; if ( null === $source ) { $errors[] = self::ERROR_UNKNOWN . ':' . $hash; continue; } if ( ! $belongsToPost( $source['id'] ) ) { $errors[] = self::ERROR_FOREIGN . ':' . $hash; continue; } $text = trim( (string) ( $segment['translated_text'] ?? '' ) ); if ( '' !== $text && ShortcodeGuard::containsShortcode( $source['source_text'] ) && ! ShortcodeGuard::isPreserved( $source['source_text'], $text ) ) { $errors[] = self::ERROR_SHORTCODE . ':' . $hash; continue; } if ( '' !== $text && Segment::KIND_ATTRIBUTE === $source['kind'] && UrlGuard::looksLikeUrl( $source['source_text'] ) && ! UrlGuard::isSafe( $text ) ) { $errors[] = self::ERROR_UNSAFE_URL . ':' . $hash; continue; } $text = Segment::KIND_HTML_BLOCK === $source['kind'] ? BlockSanitizer::sanitize( $text ) : wp_strip_all_tags( $text ); $status = (string) ( $segment['status'] ?? '' ); if ( ! TranslationStatus::isValid( $status ) ) { $status = '' !== $text ? TranslationStatus::APPROVED : TranslationStatus::MISSING; } $rows[] = array( 'id' => $source['id'], 'kind' => $source['kind'], 'text' => $text, 'status' => $status, ); } if ( array() !== $errors ) { return array( 'ok' => false, 'errors' => $errors, 'rows' => array() ); } return array( 'ok' => true, 'errors' => array(), 'rows' => $rows ); } }