Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
44.68% |
42 / 94 |
|
33.33% |
2 / 6 |
CRAP | |
0.00% |
0 / 1 |
MessageBundleContent | |
44.68% |
42 / 94 |
|
33.33% |
2 / 6 |
205.35 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isValid | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
validate | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getMessages | |
37.93% |
11 / 29 |
|
0.00% |
0 / 1 |
23.30 | |||
getMetadata | |
54.55% |
24 / 44 |
|
0.00% |
0 / 1 |
36.13 | |||
getRawData | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
30 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace MediaWiki\Extension\Translate\MessageBundleTranslation; |
5 | |
6 | use MediaWiki\Content\JsonContent; |
7 | use MediaWiki\Json\FormatJson; |
8 | use MediaWiki\Message\Message; |
9 | |
10 | /** |
11 | * @author Niklas Laxström |
12 | * @license GPL-2.0-or-later |
13 | * @since 2021.05 |
14 | */ |
15 | class MessageBundleContent extends JsonContent { |
16 | public const CONTENT_MODEL_ID = 'translate-messagebundle'; |
17 | // List of supported metadata keys |
18 | /** @phpcs-require-sorted-array */ |
19 | public const METADATA_KEYS = [ |
20 | 'allowOnlyPriorityLanguages', |
21 | 'description', |
22 | 'label', |
23 | 'priorityLanguages', |
24 | 'sourceLanguage' |
25 | ]; |
26 | private ?array $messages = null; |
27 | private ?MessageBundleMetadata $metadata = null; |
28 | |
29 | public function __construct( $text, $modelId = self::CONTENT_MODEL_ID ) { |
30 | parent::__construct( $text, $modelId ); |
31 | } |
32 | |
33 | public function isValid(): bool { |
34 | try { |
35 | $this->getMessages(); |
36 | $this->getMetadata(); |
37 | return parent::isValid(); |
38 | } catch ( MalformedBundle $e ) { |
39 | return false; |
40 | } |
41 | } |
42 | |
43 | /** @throws MalformedBundle */ |
44 | public function validate(): void { |
45 | $this->getMessages(); |
46 | $this->getMetadata(); |
47 | } |
48 | |
49 | /** @throws MalformedBundle */ |
50 | public function getMessages(): array { |
51 | if ( $this->messages !== null ) { |
52 | return $this->messages; |
53 | } |
54 | |
55 | $data = $this->getRawData(); |
56 | // Remove the metadata since we are not concerned with it. |
57 | unset( $data['@metadata'] ); |
58 | |
59 | foreach ( $data as $key => $value ) { |
60 | if ( $key === '' ) { |
61 | throw new MalformedBundle( 'translate-messagebundle-error-key-empty' ); |
62 | } |
63 | |
64 | if ( strlen( $key ) > 100 ) { |
65 | throw new MalformedBundle( |
66 | 'translate-messagebundle-error-key-too-long', |
67 | [ $key ] |
68 | ); |
69 | } |
70 | |
71 | if ( !preg_match( '/^[a-zA-Z0-9-_.]+$/', $key ) ) { |
72 | throw new MalformedBundle( |
73 | 'translate-messagebundle-error-key-invalid-characters', |
74 | [ $key ] |
75 | ); |
76 | } |
77 | |
78 | if ( !is_string( $value ) ) { |
79 | throw new MalformedBundle( |
80 | 'translate-messagebundle-error-invalid-value', |
81 | [ $key ] |
82 | ); |
83 | } |
84 | |
85 | if ( trim( $value ) === '' ) { |
86 | throw new MalformedBundle( |
87 | 'translate-messagebundle-error-empty-value', |
88 | [ $key ] |
89 | ); |
90 | } |
91 | } |
92 | |
93 | $this->messages = $data; |
94 | return $this->messages; |
95 | } |
96 | |
97 | public function getMetadata(): MessageBundleMetadata { |
98 | if ( $this->metadata !== null ) { |
99 | return $this->metadata; |
100 | } |
101 | |
102 | $data = $this->getRawData(); |
103 | $metadata = $data['@metadata'] ?? []; |
104 | |
105 | if ( !is_array( $metadata ) ) { |
106 | throw new MalformedBundle( 'translate-messagebundle-error-metadata-type' ); |
107 | } |
108 | |
109 | foreach ( $metadata as $key => $value ) { |
110 | if ( !in_array( $key, self::METADATA_KEYS ) ) { |
111 | throw new MalformedBundle( |
112 | 'translate-messagebundle-error-invalid-metadata', |
113 | [ $key, Message::listParam( self::METADATA_KEYS ) ] |
114 | ); |
115 | } |
116 | } |
117 | |
118 | $sourceLanguage = $metadata['sourceLanguage'] ?? null; |
119 | if ( $sourceLanguage && !is_string( $sourceLanguage ) ) { |
120 | throw new MalformedBundle( |
121 | 'translate-messagebundle-error-invalid-sourcelanguage', [ $sourceLanguage ] |
122 | ); |
123 | } |
124 | |
125 | $priorityLanguageCodes = $metadata['priorityLanguages'] ?? null; |
126 | if ( $priorityLanguageCodes ) { |
127 | if ( !is_array( $priorityLanguageCodes ) ) { |
128 | throw new MalformedBundle( 'translate-messagebundle-error-invalid-prioritylanguage-format' ); |
129 | } |
130 | |
131 | $priorityLanguageCodes = array_unique( $priorityLanguageCodes ); |
132 | } |
133 | |
134 | $description = $metadata['description'] ?? null; |
135 | if ( $description !== null ) { |
136 | if ( !is_string( $description ) ) { |
137 | throw new MalformedBundle( |
138 | 'translate-messagebundle-error-invalid-description' |
139 | ); |
140 | } |
141 | |
142 | $description = trim( $description ) === '' ? null : trim( $description ); |
143 | } |
144 | |
145 | $label = $metadata['label'] ?? null; |
146 | if ( $label !== null ) { |
147 | if ( !is_string( $label ) ) { |
148 | throw new MalformedBundle( |
149 | 'translate-messagebundle-error-invalid-label' |
150 | ); |
151 | } |
152 | |
153 | $label = trim( $label ) === '' ? null : trim( $label ); |
154 | } |
155 | |
156 | $this->metadata = new MessageBundleMetadata( |
157 | $sourceLanguage, |
158 | $priorityLanguageCodes, |
159 | (bool)( $metadata['allowOnlyPriorityLanguages'] ?? false ), |
160 | $description, |
161 | $label |
162 | ); |
163 | return $this->metadata; |
164 | } |
165 | |
166 | private function getRawData(): array { |
167 | $status = FormatJson::parse( $this->getText(), FormatJson::FORCE_ASSOC ); |
168 | if ( !$status->isOK() ) { |
169 | throw new MalformedBundle( |
170 | 'translate-messagebundle-error-parsing', |
171 | [ $status->getMessages( 'error' )[0] ] |
172 | ); |
173 | } |
174 | |
175 | $data = $status->getValue(); |
176 | // Crude check that we have an associative array (or empty array) |
177 | if ( !is_array( $data ) || ( $data !== [] && array_values( $data ) === $data ) ) { |
178 | throw new MalformedBundle( |
179 | 'translate-messagebundle-error-invalid-array', |
180 | [ gettype( $data ) ] |
181 | ); |
182 | } |
183 | |
184 | return $data; |
185 | } |
186 | } |