Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
MessageBundleContent.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\MessageBundleTranslation;
5
6use FormatJson;
7use JsonContent;
8use Message;
9use Status;
10use User;
11use WikiPage;
12
18class MessageBundleContent extends JsonContent {
19 public const CONTENT_MODEL_ID = 'translate-messagebundle';
20 // List of supported metadata keys
22 public const METADATA_KEYS = [
23 'allowOnlyPriorityLanguages',
24 'description',
25 'priorityLanguages',
26 'sourceLanguage'
27 ];
29 private $messages;
31 private $metadata;
32
33 public function __construct( $text, $modelId = self::CONTENT_MODEL_ID ) {
34 parent::__construct( $text, $modelId );
35 }
36
37 public function isValid(): bool {
38 try {
39 $this->getMessages();
40 $this->getMetadata();
41 return parent::isValid();
42 } catch ( MalformedBundle $e ) {
43 return false;
44 }
45 }
46
48 public function validate(): void {
49 $this->getMessages();
50 $this->getMetadata();
51 }
52
53 public function prepareSave( WikiPage $page, $flags, $parentRevId, User $user ) {
54 // TODO: Should be removed when it is no longer needed for backwards compatibility.
55
56 // This will give an informative error message when trying to change the content model
57 try {
58 $this->getMessages();
59 $this->getMetadata();
60 return Status::newGood();
61 } catch ( MalformedBundle $e ) {
62 // XXX: We have no context source nor is there Message::messageParam :(
63 return Status::newFatal( 'translate-messagebundle-validation-error', wfMessage( $e ) );
64 }
65 }
66
68 public function getMessages(): array {
69 if ( $this->messages ) {
70 return $this->messages;
71 }
72
73 $data = $this->getRawData();
74 // Remove the metadata since we are not concerned with it.
75 unset( $data['@metadata'] );
76
77 foreach ( $data as $key => $value ) {
78 if ( $key === '' ) {
79 throw new MalformedBundle( 'translate-messagebundle-error-key-empty' );
80 }
81
82 if ( strlen( $key ) > 100 ) {
83 throw new MalformedBundle(
84 'translate-messagebundle-error-key-too-long',
85 [ $key ]
86 );
87 }
88
89 if ( !preg_match( '/^[a-zA-Z0-9-_.]+$/', $key ) ) {
90 throw new MalformedBundle(
91 'translate-messagebundle-error-key-invalid-characters',
92 [ $key ]
93 );
94 }
95
96 if ( !is_string( $value ) ) {
97 throw new MalformedBundle(
98 'translate-messagebundle-error-invalid-value',
99 [ $key ]
100 );
101 }
102
103 if ( trim( $value ) === '' ) {
104 throw new MalformedBundle(
105 'translate-messagebundle-error-empty-value',
106 [ $key ]
107 );
108 }
109 }
110
111 $this->messages = $data;
112 return $this->messages;
113 }
114
115 public function getMetadata(): MessageBundleMetadata {
116 if ( $this->metadata ) {
117 return $this->metadata;
118 }
119
120 $data = $this->getRawData();
121 $metadata = $data['@metadata'] ?? [];
122
123 if ( !is_array( $metadata ) ) {
124 throw new MalformedBundle( 'translate-messagebundle-error-metadata-type' );
125 }
126
127 foreach ( $metadata as $key => $value ) {
128 if ( !in_array( $key, self::METADATA_KEYS ) ) {
129 throw new MalformedBundle(
130 'translate-messagebundle-error-invalid-metadata',
131 [ $key, Message::listParam( self::METADATA_KEYS ) ]
132 );
133 }
134 }
135
136 $sourceLanguage = $metadata['sourceLanguage'] ?? null;
137 if ( $sourceLanguage && !is_string( $sourceLanguage ) ) {
138 throw new MalformedBundle(
139 'translate-messagebundle-error-invalid-sourcelanguage', [ $sourceLanguage ]
140 );
141 }
142
143 $priorityLanguageCodes = $metadata['priorityLanguages'] ?? null;
144 if ( $priorityLanguageCodes ) {
145 if ( !is_array( $priorityLanguageCodes ) ) {
146 throw new MalformedBundle( 'translate-messagebundle-error-invalid-prioritylanguage-format' );
147 }
148
149 $priorityLanguageCodes = array_unique( $priorityLanguageCodes );
150 }
151
152 $description = $metadata['description'] ?? null;
153 if ( $description !== null ) {
154 if ( !is_string( $description ) ) {
155 throw new MalformedBundle(
156 'translate-messagebundle-error-invalid-description'
157 );
158 }
159
160 $description = trim( $description ) === '' ? null : trim( $description );
161 }
162
163 $this->metadata = new MessageBundleMetadata(
164 $sourceLanguage,
165 $priorityLanguageCodes,
166 (bool)( $metadata['allowOnlyPriorityLanguages'] ?? false ),
167 $description
168 );
169 return $this->metadata;
170 }
171
172 private function getRawData(): array {
173 $status = FormatJson::parse( $this->getText(), FormatJson::FORCE_ASSOC );
174 if ( !$status->isOK() ) {
175 throw new MalformedBundle(
176 'translate-messagebundle-error-parsing',
177 [ $status->getMessage()->text() ]
178 );
179 }
180
181 $data = $status->getValue();
182 // Crude check that we have an associative array (or empty array)
183 if ( !is_array( $data ) || ( $data !== [] && array_values( $data ) === $data ) ) {
184 throw new MalformedBundle(
185 'translate-messagebundle-error-invalid-array',
186 [ gettype( $data ) ]
187 );
188 }
189
190 return $data;
191 }
192}