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