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