Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.56% covered (success)
97.56%
80 / 82
88.89% covered (warning)
88.89%
8 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
ZLangRegistry
97.56% covered (success)
97.56%
80 / 82
88.89% covered (warning)
88.89%
8 / 9
23
0.00% covered (danger)
0.00%
0 / 1
 initialize
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getLanguageCodeFromZid
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getLanguageZidFromCode
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
4
 isLanguageKnownGivenCode
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 fetchLanguageCodeFromZid
93.55% covered (success)
93.55%
29 / 31
0.00% covered (danger)
0.00%
0 / 1
4.00
 getLanguageCodeFromContent
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 isValidLanguageZid
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 getLanguageZids
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getListOfFallbackLanguageZids
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * WikiLambda ZLangRegistry
4 *
5 * @file
6 * @ingroup Extensions
7 * @copyright 2020– Abstract Wikipedia team; see AUTHORS.txt
8 * @license MIT
9 */
10
11namespace MediaWiki\Extension\WikiLambda\Registry;
12
13use MediaWiki\Extension\WikiLambda\WikiLambdaServices;
14use MediaWiki\Extension\WikiLambda\ZErrorException;
15use MediaWiki\Extension\WikiLambda\ZErrorFactory;
16use MediaWiki\Extension\WikiLambda\ZObjectContent;
17use MediaWiki\Extension\WikiLambda\ZObjectUtils;
18use MediaWiki\Languages\LanguageFallback;
19use MediaWiki\Logger\LoggerFactory;
20use MediaWiki\Title\Title;
21
22/**
23 * A registry service for ZLanguage and language codes
24 */
25class ZLangRegistry extends ZObjectRegistry {
26
27    private const FALLBACK_LANGUAGE_ZID = 'Z1002';
28    private const FALLBACK_LANGUAGE_CODE = 'en';
29    public const MULTILINGUAL_VALUE = 'Z1360';
30
31    /**
32     * Initialize ZLangRegistry
33     */
34    protected function initialize(): void {
35        // Registry for ZObjects of type ZLanguage/Z60
36        $this->type = ZTypeRegistry::Z_LANGUAGE;
37
38        $this->register(
39            self::FALLBACK_LANGUAGE_ZID,
40            self::FALLBACK_LANGUAGE_CODE
41        );
42    }
43
44    /**
45     * Given a ZLanguage Zid, return its language code
46     *
47     * @param string $zid
48     * @return string
49     * @throws ZErrorException
50     */
51    public function getLanguageCodeFromZid( $zid ): string {
52        if ( array_key_exists( $zid, $this->registry ) ) {
53            return $this->registry[ $zid ];
54        }
55
56        $code = $this->fetchLanguageCodeFromZid( $zid );
57        $this->register( $zid, $code );
58        return $code;
59    }
60
61    /**
62     * Given a language code, return its ZLanguage Zid
63     *
64     * @param string $code
65     * @param bool $fallback If true, give the ZLanguage for English
66     * @return string
67     * @throws ZErrorException
68     */
69    public function getLanguageZidFromCode( $code, $fallback = false ): string {
70        $zid = array_search( $code, $this->registry );
71        if ( $zid ) {
72            return $zid;
73        }
74
75        // Not in the registry, but let's check the DB
76        $zObjectStore = WikiLambdaServices::getZObjectStore();
77        $zid = $zObjectStore->findZLanguageFromCode( $code );
78
79        if ( $zid === null ) {
80            if ( $fallback ) {
81                return self::FALLBACK_LANGUAGE_ZID;
82            }
83            throw new ZErrorException(
84                ZErrorFactory::createZErrorInstance(
85                    ZErrorTypeRegistry::Z_ERROR_LANG_NOT_FOUND,
86                    [ 'lang' => $code ]
87                )
88            );
89        }
90
91        // Add it to the register now for faster lookups later
92        $this->register( $zid, $code );
93        return $zid;
94    }
95
96    /**
97     * Check if a given language code, is a known ZLanguage Zid
98     *
99     * @param string $code
100     * @return bool
101     */
102    public function isLanguageKnownGivenCode( $code ): bool {
103        try {
104            $this->getLanguageZidFromCode( $code );
105        } catch ( \Throwable $th ) {
106            return false;
107        }
108        return true;
109    }
110
111    /**
112     * Fetch zid from the database, parse it and return its language code.
113     *
114     * @param string $zid
115     * @return string The language code of the ZLanguage identified by this Zid
116     * @throws ZErrorException
117     */
118    private function fetchLanguageCodeFromZid( $zid ): string {
119        $zObjectStore = WikiLambdaServices::getZObjectStore();
120
121        // Try the cache table, where it should be available
122        $languages = $zObjectStore->findCodesFromZLanguage( $zid );
123
124        if ( count( $languages ) ) {
125            // Return the first, in case there are aliases
126            return $languages[0];
127        }
128
129        // Fallback to the database just in case it's somehow not cached.
130        $logger = LoggerFactory::getInstance( 'WikiLambda' );
131        $logger->warning(
132            'Called fetchLanguageCodeFromZid but not found in cache table: {zid}',
133            [ 'zid' => $zid ]
134        );
135
136        $title = Title::newFromText( $zid, NS_MAIN );
137        $content = $zObjectStore->fetchZObjectByTitle( $title );
138        if ( !$content ) {
139            throw new ZErrorException(
140                ZErrorFactory::createZErrorInstance(
141                    ZErrorTypeRegistry::Z_ERROR_ZID_NOT_FOUND,
142                    [ 'data' => $zid ]
143                )
144            );
145        }
146
147        $code = $this->getLanguageCodeFromContent( $content );
148        if ( !$code ) {
149            throw new ZErrorException(
150                ZErrorFactory::createZErrorInstance(
151                    ZErrorTypeRegistry::Z_ERROR_MISSING_KEY,
152                    [
153                        'data' => $content->getObject(),
154                        'keywordArgs' => [ 'missing' => ZTypeRegistry::Z_LANGUAGE_CODE ]
155                    ]
156                )
157            );
158        }
159
160        // Re-insert into the languages cache so we don't have this expensive miss again.
161        $zObjectStore->insertZLanguageToLanguagesCache( $zid, $code );
162
163        return $code;
164    }
165
166    /**
167     * Returns the language code from a ZObjectContent wrapping a Z60.
168     *
169     * @param ZObjectContent $content
170     * @return string|bool Language code or false if content object is not valid Z60.
171     */
172    private function getLanguageCodeFromContent( $content ) {
173        $zobject = $content->getObject()->{ ZTypeRegistry::Z_PERSISTENTOBJECT_VALUE };
174        if (
175            ( $zobject->{ZTypeRegistry::Z_OBJECT_TYPE} === ZTypeRegistry::Z_LANGUAGE ) &&
176            ( property_exists( $zobject, ZTypeRegistry::Z_LANGUAGE_CODE ) )
177        ) {
178            return $zobject->{ZTypeRegistry::Z_LANGUAGE_CODE};
179        }
180        return false;
181    }
182
183    /**
184     * Checks if the given Zid is a valid language Zid. For that it first checks whether the
185     * Zid is registered, and if it's not, it fetches it from the database.
186     *
187     * @param string $zid
188     * @return bool Is a valid ZLanguage Zid
189     */
190    public function isValidLanguageZid( $zid ) {
191        if ( !ZObjectUtils::isValidZObjectReference( $zid ) ) {
192            return false;
193        }
194        try {
195            $this->getLanguageCodeFromZid( $zid );
196        } catch ( ZErrorException $e ) {
197            return false;
198        }
199        return true;
200    }
201
202    /**
203     * Returns an array of language Zids given an array of language codes
204     *
205     * @param string[] $languageCodes
206     * @return string[]
207     */
208    public function getLanguageZids( $languageCodes ) {
209        $languageZids = [];
210        foreach ( $languageCodes as $code ) {
211            try {
212                $languageZids[] = $this->getLanguageZidFromCode( $code );
213            } catch ( ZErrorException $e ) {
214                // We ignore the language code if it's not available as Zid
215            }
216        }
217        return $languageZids;
218    }
219
220    /**
221     * Return the list of unique language Zids that correspond
222     * to the user's selected language, its fallbacks, and English
223     * if requested.
224     *
225     * @param LanguageFallback $languageFallback
226     * @param string $langCode - Language BCP47 code
227     * @return string[]
228     */
229    public function getListOfFallbackLanguageZids( $languageFallback, $langCode ) {
230        $languages = array_merge(
231            [ $langCode ],
232            $languageFallback->getAll( $langCode, LanguageFallback::MESSAGES )
233        );
234        return $this->getLanguageZids( array_unique( $languages ) );
235    }
236}