Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
73.77% covered (warning)
73.77%
45 / 61
81.82% covered (warning)
81.82%
9 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
ZMultiLingualString
73.77% covered (warning)
73.77%
45 / 61
81.82% covered (warning)
81.82%
9 / 11
28.96
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 getDefinition
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 isValid
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 getSerialized
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 getZValue
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getValueAsList
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getStringForLanguageCode
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 isLanguageProvidedValue
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getStringForLanguage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 buildStringForLanguage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setMonoLingualString
21.05% covered (danger)
21.05%
4 / 19
0.00% covered (danger)
0.00%
0 / 1
7.43
1<?php
2/**
3 * WikiLambda ZMultiLingualString
4 *
5 * @file
6 * @ingroup Extensions
7 * @copyright 2020– Abstract Wikipedia team; see AUTHORS.txt
8 * @license MIT
9 */
10
11namespace MediaWiki\Extension\WikiLambda\ZObjects;
12
13use MediaWiki\Extension\WikiLambda\Registry\ZLangRegistry;
14use MediaWiki\Extension\WikiLambda\Registry\ZTypeRegistry;
15use MediaWiki\Extension\WikiLambda\ZErrorException;
16use MediaWiki\Extension\WikiLambda\ZObjectUtils;
17use MediaWiki\Language\Language;
18use MediaWiki\Logger\LoggerFactory;
19
20class ZMultiLingualString extends ZObject {
21
22    /**
23     * Create a ZMultiLingualString instance given an array or a ZTypedList of
24     * ZMonoLingualString instances. Internally this class bypasses ZTypedList
25     * and stores an array with the language Zid as key.
26     *
27     * @param ZTypedList|array $strings
28     */
29    public function __construct( $strings = [] ) {
30        foreach ( ZObjectUtils::getIterativeList( $strings ) as $index => $monoLingualString ) {
31            if ( $monoLingualString instanceof ZMonoLingualString ) {
32                $this->setMonoLingualString( $monoLingualString );
33            }
34        }
35    }
36
37    /**
38     * @inheritDoc
39     */
40    public static function getDefinition(): array {
41        return [
42            'type' => [
43                'type' => ZTypeRegistry::Z_REFERENCE,
44                'value' => ZTypeRegistry::Z_MULTILINGUALSTRING,
45            ],
46            'keys' => [
47                ZTypeRegistry::Z_MULTILINGUALSTRING_VALUE => [
48                    'type' => ZTypeRegistry::HACK_ARRAY_Z_MONOLINGUALSTRING,
49                    'required' => true,
50                ],
51            ],
52        ];
53    }
54
55    /**
56     * @inheritDoc
57     */
58    public function isValid(): bool {
59        // @phan-suppress-next-line PhanTypeSuspiciousNonTraversableForeach; it's a ZMonoLingualString[]
60        foreach ( $this->data[ ZTypeRegistry::Z_MULTILINGUALSTRING_VALUE ] ?? [] as $lang => $monolingualString ) {
61            if ( !$monolingualString->isValid() ) {
62                return false;
63            }
64        }
65        return true;
66    }
67
68    /**
69     * @inheritDoc
70     */
71    public function getSerialized( $form = self::FORM_CANONICAL ) {
72        $listType = new ZReference( ZTypeRegistry::Z_MONOLINGUALSTRING );
73        $typedList = new ZTypedList( ZTypedList::buildType( $listType ), array_values( $this->getZValue() ) );
74        return (object)[
75            ZTypeRegistry::Z_OBJECT_TYPE => $this->getZTypeObject()->getSerialized( $form ),
76            ZTypeRegistry::Z_MULTILINGUALSTRING_VALUE => $typedList->getSerialized( $form )
77        ];
78    }
79
80    /**
81     * Get the list of ZMonoLingualStrings that represent the value of this ZMultiLingualString
82     *
83     * @return array
84     */
85    public function getZValue() {
86        return $this->data[ ZTypeRegistry::Z_MULTILINGUALSTRING_VALUE ] ?? [];
87    }
88
89    /**
90     * Get the values of this ZMultiLingualString in the shape of an array
91     * with language as key and string as value
92     *
93     * @return array
94     */
95    public function getValueAsList() {
96        $multi = [];
97        foreach ( $this->getZValue() as $mono ) {
98            $multi[ $mono->getLanguage() ] = $mono->getString();
99        }
100        return $multi;
101    }
102
103    /**
104     * Fetch the ZMultiLingualString's stored value for a given MediaWiki language code (e.g.
105     * 'en' or 'zh-hant'). Note that this is a raw fetch and does not walk the language fallback
106     * chain; users are expected to use getStringForLanguage() which does.
107     *
108     * @param string $languageCode The MediaWiki language code in which the string is wanted.
109     * @return string The string, or the empty string if .
110     */
111    public function getStringForLanguageCode( string $languageCode ): string {
112        try {
113            $languageZid = ZLangRegistry::singleton()->getLanguageZidFromCode( $languageCode );
114        } catch ( ZErrorException ) {
115            return '';
116        }
117        return array_key_exists( $languageZid, $this->getZValue() )
118            ? $this->getZValue()[ $languageZid ]->getString()
119            : '';
120    }
121
122    /**
123     * Check if the ZMultiLingualString has a stored value for a given MediaWiki language code (e.g.
124     * 'en' or 'zh-hant'). Note that this is a raw check and does not walk the language fallback
125     * chain.
126     *
127     * @param string $languageCode The MediaWiki language code in which the string is wanted.
128     * @return bool If there is a string stored.
129     */
130    public function isLanguageProvidedValue( string $languageCode ): bool {
131        try {
132            $languageZid = ZLangRegistry::singleton()->getLanguageZidFromCode( $languageCode );
133        } catch ( ZErrorException ) {
134            return false;
135        }
136        return array_key_exists( $languageZid, $this->data[ ZTypeRegistry::Z_MULTILINGUALSTRING_VALUE ] ?? [] );
137    }
138
139    /**
140     * Fetch the ZMultiLingualString's stored value for a given MediaWiki language class. This will
141     * walk the language fallback chain, and provide a fallback message if there is no label defined
142     * in the given language or any of its fallback languages.
143     *
144     * @param Language $language The MediaWiki language class in which the string is wanted.
145     * @return string|null The string, the value of the wikilambda-multilingualstring-nofallback message, or null.
146     */
147    public function getStringForLanguage( Language $language ): ?string {
148        return $this->buildStringForLanguage( $language )->placeholderNoFallback()->getString();
149    }
150
151    /**
152     * Instantiate a chained builder with which you can ask for a fallback in English,
153     * or define a placeholder. At the end, either call `getString()` or `getStringAndLanguageCode()`.
154     *
155     * @param Language $language
156     * @return StringForLanguageBuilder
157     */
158    public function buildStringForLanguage( Language $language ): StringForLanguageBuilder {
159        return new StringForLanguageBuilder( $language, $this );
160    }
161
162    /**
163     * Add a ZMonoLingualString to the ZMultilingualString object.
164     *
165     * As there is no dynamic use of these classes (only pre-write or pre-read
166     * instantiation), setting a language that already exists is not a "set"
167     * or "replace" nuclear operation, but a detection of a malformed object.
168     *
169     * @param ZMonoLingualString $value The new value to set.
170     * @throws ZErrorException
171     */
172    public function setMonoLingualString( ZMonoLingualString $value ): void {
173        $language = $value->getLanguage();
174        if ( !is_string( $language ) ) {
175            $logger = LoggerFactory::getInstance( 'WikiLambda' );
176            $logger->warning(
177                'Label to be added to a ZMultiLingualString but not a string: {language}',
178                [
179                    'language' => $language,
180                    'value' => $value,
181                    'e' => new \InvalidArgumentException()
182                ]
183            );
184            return;
185        }
186        if ( array_key_exists( $language, $this->getZValue() ) ) {
187            // When detecting a duplicate language, we log a note so that
188            // we can correct the content issue, but we should not be throwing
189            // an exception, as this will break unrelated objects, too.
190            $logger = LoggerFactory::getInstance( 'WikiLambda' );
191            $logger->info(
192                'Duplicate language in a MultiLingual String is not allowed: {language}',
193                [ 'language' => $language ]
194            );
195        }
196        $this->data[ ZTypeRegistry::Z_MULTILINGUALSTRING_VALUE ][ $language ] = $value;
197    }
198}