Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
68.18% covered (warning)
68.18%
45 / 66
33.33% covered (danger)
33.33%
2 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
TranslationAidDataProvider
68.18% covered (warning)
68.18%
45 / 66
33.33% covered (danger)
33.33%
2 / 6
24.25
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getDefinition
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 hasDefinition
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getDefinitionContent
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getGoodTranslations
88.24% covered (warning)
88.24%
15 / 17
0.00% covered (danger)
0.00%
0 / 1
4.03
 loadTranslationData
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\TranslatorInterface\Aid;
5
6use Content;
7use ContentHandler;
8use MediaWiki\Extension\Translate\MessageGroupProcessing\RevTagStore;
9use MediaWiki\Extension\Translate\MessageLoading\MessageHandle;
10use MediaWiki\Extension\Translate\TranslatorInterface\TranslationHelperException;
11use MediaWiki\MediaWikiServices;
12use MediaWiki\Revision\RevisionRecord;
13use MediaWiki\Revision\SlotRecord;
14use MessageGroup;
15use TextContent;
16use Wikimedia\Rdbms\IDatabase;
17
18/**
19 * @author Niklas Laxström
20 * @license GPL-2.0-or-later
21 * @since 2018.01
22 */
23class TranslationAidDataProvider {
24    /** @var MessageHandle */
25    private $handle;
26    /** @var MessageGroup */
27    private $group;
28    /** @var string|null */
29    private $definition;
30    /** @var array */
31    private $translations;
32
33    public function __construct( MessageHandle $handle ) {
34        $this->handle = $handle;
35        $this->group = $handle->getGroup();
36    }
37
38    /**
39     * Get the message definition. Cached for performance.
40     * @return string
41     */
42    public function getDefinition(): string {
43        if ( $this->definition !== null ) {
44            return $this->definition;
45        }
46
47        // Optional performance optimization
48        if ( method_exists( $this->group, 'getMessageContent' ) ) {
49            // @phan-suppress-next-line PhanUndeclaredMethod
50            $this->definition = $this->group->getMessageContent( $this->handle );
51        } else {
52            $this->definition = $this->group->getMessage(
53                $this->handle->getKey(),
54                $this->group->getSourceLanguage()
55            );
56        }
57
58        if ( $this->definition === null ) {
59            throw new TranslationHelperException(
60                'Did not find message definition for ' . $this->handle->getTitle()->getPrefixedText() .
61                ' in group ' . $this->group->getId()
62            );
63        }
64        return $this->definition;
65    }
66
67    public function hasDefinition(): bool {
68        try {
69            $this->getDefinition();
70            return true;
71        } catch ( TranslationHelperException $e ) {
72            return false;
73        }
74    }
75
76    public function getDefinitionContent(): Content {
77        return ContentHandler::makeContent( $this->getDefinition(), $this->handle->getTitle() );
78    }
79
80    /**
81     * Get the translations in all languages. Cached for performance.
82     * Fuzzy translation are not included.
83     * @return array Language code => Translation
84     */
85    public function getGoodTranslations(): array {
86        if ( $this->translations !== null ) {
87            return $this->translations;
88        }
89
90        $mwServices = MediaWikiServices::getInstance();
91        $data = self::loadTranslationData(
92            $mwServices->getDBLoadBalancer()->getConnection( DB_REPLICA ),
93            $this->handle
94        );
95        $translations = [];
96        $prefixLength = strlen( $this->handle->getTitleForBase()->getDBkey() . '/' );
97        $languageNameUtils = $mwServices->getLanguageNameUtils();
98
99        foreach ( $data as $page => $translation ) {
100            // Could use MessageHandle here, but that queries the message index.
101            // Instead, we can get away with simple string manipulation.
102            $code = substr( $page, $prefixLength );
103            if ( !$languageNameUtils->isKnownLanguageTag( $code ) ) {
104                continue;
105            }
106
107            $translations[ $code ] = $translation;
108        }
109
110        $this->translations = $translations;
111
112        return $translations;
113    }
114
115    private static function loadTranslationData( IDatabase $db, MessageHandle $handle ): array {
116        $revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
117        $queryInfo = $revisionStore->getQueryInfo( [ 'page' ] );
118        $conds = [];
119
120        // The list of pages we want to select, and their latest versions
121        $conds['page_namespace'] = $handle->getTitle()->getNamespace();
122        $base = $handle->getKey();
123        $conds[] = 'page_title ' . $db->buildLike( "$base/", $db->anyString() );
124        $conds[] = 'rev_id=page_latest';
125
126        // For fuzzy tags we need the join with revtag and also:
127        $conds[ 'rt_type' ] = null;
128
129        // TODO Migrate to RevisionStore::newSelectQueryBuilder once we support >= 1.41
130        $rows = $db->newSelectQueryBuilder()
131            ->tables( $queryInfo[ 'tables' ] )
132            ->fields( $queryInfo[ 'fields' ] )
133            ->leftJoin( 'revtag', null, [
134                'page_id=rt_page',
135                'page_latest=rt_revision',
136                'rt_type' => RevTagStore::FUZZY_TAG
137            ] )
138            ->where( $conds )
139            ->joinConds( $queryInfo[ 'joins' ] )
140            ->caller( __METHOD__ )
141            ->fetchResultSet();
142
143        $pages = [];
144        $revisions = $revisionStore->newRevisionsFromBatch( $rows, [ 'slots' => [ SlotRecord::MAIN ] ] )
145            ->getValue();
146        foreach ( $rows as $row ) {
147            /** @var RevisionRecord|null $rev */
148            $rev = $revisions[$row->rev_id];
149            if ( $rev && $rev->getContent( SlotRecord::MAIN ) instanceof TextContent ) {
150                $pages[$row->page_title] = $rev->getContent( SlotRecord::MAIN )->getText();
151            }
152        }
153
154        return $pages;
155    }
156}