Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
QueryMessageCollectionActionApi.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\MessageLoading;
5
6use ApiBase;
7use ApiPageSet;
8use ApiQuery;
9use ApiQueryGeneratorBase;
10use ApiResult;
12use MediaWiki\Languages\LanguageNameUtils;
15use MWException;
16use Title;
17use TMessage;
19use Wikimedia\ParamValidator\ParamValidator;
20use Wikimedia\ParamValidator\TypeDef\IntegerDef;
21use Wikimedia\Rdbms\ILoadBalancer;
22
29class QueryMessageCollectionActionApi extends ApiQueryGeneratorBase {
31 private $configHelper;
33 private $languageNameUtils;
35 private $loadBalancer;
36
37 public function __construct(
38 ApiQuery $query,
39 string $moduleName,
40 ConfigHelper $configHelper,
41 LanguageNameUtils $languageNameUtils,
42 ILoadBalancer $loadBalancer
43 ) {
44 parent::__construct( $query, $moduleName, 'mc' );
45 $this->configHelper = $configHelper;
46 $this->languageNameUtils = $languageNameUtils;
47 $this->loadBalancer = $loadBalancer;
48 }
49
50 public function execute(): void {
51 $this->run();
52 }
53
55 public function getCacheMode( $params ): string {
56 return 'public';
57 }
58
60 public function executeGenerator( $resultPageSet ): void {
61 $this->run( $resultPageSet );
62 }
63
64 private function validateLanguageCode( string $code ): void {
65 if ( !TranslateUtils::isSupportedLanguageCode( $code ) ) {
66 $this->dieWithError( [ 'apierror-translate-invalidlanguage', $code ] );
67 }
68 }
69
70 private function run( ApiPageSet $resultPageSet = null ): void {
71 $params = $this->extractRequestParams();
72
73 $group = MessageGroups::getGroup( $params['group'] );
74 if ( !$group ) {
75 $this->dieWithError( [ 'apierror-badparameter', 'mcgroup' ] );
76 }
77
78 $languageCode = $params[ 'language' ];
79 $this->validateLanguageCode( $languageCode );
80
81 // Even though translation to source language maybe disabled, we still want to
82 // fetch the message collections for the source language.
83 if ( $group->getSourceLanguage() === $languageCode ) {
84 $name = $this->getLanguageName( $languageCode );
85 $this->addWarning( [ 'apiwarn-translate-language-disabled-source', wfEscapeWikiText( $name ) ] );
86 } else {
87 $languages = $group->getTranslatableLanguages();
88 if ( $languages === null ) {
89 $checks = [
90 $group->getId(),
91 strtok( $group->getId(), '-' ),
92 '*'
93 ];
94
95 $disabledLanguages = $this->configHelper->getDisabledTargetLanguages();
96 foreach ( $checks as $check ) {
97 if ( isset( $disabledLanguages[ $check ][ $languageCode ] ) ) {
98 $name = $this->getLanguageName( $languageCode );
99 $reason = $disabledLanguages[ $check ][ $languageCode ];
100 $this->dieWithError( [ 'apierror-translate-language-disabled-reason', $name, $reason ] );
101 }
102 }
103 } elseif ( !isset( $languages[ $languageCode ] ) ) {
104 // Not a translatable language
105 $name = $this->getLanguageName( $languageCode );
106 $this->dieWithError( [ 'apierror-translate-language-disabled', $name ] );
107 }
108 }
109
110 if ( MessageGroups::isDynamic( $group ) ) {
112 // @phan-suppress-next-line PhanUndeclaredMethod
113 $group->setLanguage( $params['language'] );
114 }
115
116 $messages = $group->initCollection( $params['language'] );
117
118 foreach ( $params['filter'] as $filter ) {
119 $value = null;
120 if ( strpos( $filter, ':' ) !== false ) {
121 list( $filter, $value ) = explode( ':', $filter, 2 );
122 }
123 /* The filtering params here are swapped wrt MessageCollection.
124 * There (fuzzy) means do not show fuzzy, which is the same as !fuzzy
125 * here and fuzzy here means (fuzzy, false) there. */
126 try {
127 if ( $filter[0] === '!' ) {
128 $messages->filter( substr( $filter, 1 ), true, $value );
129 } else {
130 $messages->filter( $filter, false, $value );
131 }
132 } catch ( MWException $e ) {
133 $this->dieWithError(
134 [ 'apierror-translate-invalidfilter', wfEscapeWikiText( $e->getMessage() ) ],
135 'invalidfilter'
136 );
137 }
138 }
139
140 $resultSize = count( $messages );
141 $offsets = $messages->slice( $params['offset'], $params['limit'] );
142 $batchSize = count( $messages );
143 list( /*$backwardsOffset*/, $forwardsOffset, $startOffset ) = $offsets;
144
145 $result = $this->getResult();
146 $result->addValue(
147 [ 'query', 'metadata' ],
148 'state',
149 self::getWorkflowState( $group->getId(), $params['language'] )
150 );
151
152 $result->addValue( [ 'query', 'metadata' ], 'resultsize', $resultSize );
153 $result->addValue(
154 [ 'query', 'metadata' ],
155 'remaining',
156 $resultSize - $startOffset - $batchSize
157 );
158
159 $messages->loadTranslations();
160
161 $pages = [];
162
163 if ( $forwardsOffset !== false ) {
164 $this->setContinueEnumParameter( 'offset', $forwardsOffset );
165 }
166
167 $props = array_flip( $params['prop'] );
168
170 foreach ( $messages->keys() as $mkey => $titleValue ) {
171 $title = Title::newFromLinkTarget( $titleValue );
172
173 if ( $resultPageSet === null ) {
174 $data = $this->extractMessageData( $result, $props, $messages[$mkey] );
175 $data['title'] = $title->getPrefixedText();
176 $data['targetLanguage'] = $messages->getLanguage();
177
178 $handle = new MessageHandle( $title );
179
180 if ( $handle->isValid() ) {
181 $data['primaryGroup'] = $handle->getGroup()->getId();
182 }
183
184 $result->addValue( [ 'query', $this->getModuleName() ], null, $data );
185 } else {
186 $pages[] = $title;
187 }
188 }
189
190 if ( $resultPageSet === null ) {
191 $result->addIndexedTagName(
192 [ 'query', $this->getModuleName() ],
193 'message'
194 );
195 } else {
196 $resultPageSet->populateFromTitles( $pages );
197 }
198 }
199
200 private function getLanguageName( string $languageCode ): string {
201 return $this
202 ->languageNameUtils
203 ->getLanguageName( $languageCode, $this->getLanguage()->getCode() );
204 }
205
206 private function extractMessageData(
207 ApiResult $result,
208 array $props,
209 TMessage $message
210 ): array {
211 $data = [ 'key' => $message->key() ];
212
213 if ( isset( $props['definition'] ) ) {
214 $data['definition'] = $message->definition();
215 }
216 if ( isset( $props['translation'] ) ) {
217 // Remove !!FUZZY!! from translation if present.
218 $translation = $message->translation();
219 if ( $translation !== null ) {
220 $translation = str_replace( TRANSLATE_FUZZY, '', $translation );
221 }
222 $data['translation'] = $translation;
223 }
224 if ( isset( $props['tags'] ) ) {
225 $data['tags'] = $message->getTags();
226 $result->setIndexedTagName( $data['tags'], 'tag' );
227 }
228 // BC
229 if ( isset( $props['revision'] ) ) {
230 $data['revision'] = $message->getProperty( 'revision' );
231 }
232 if ( isset( $props['properties'] ) ) {
233 foreach ( $message->getPropertyNames() as $prop ) {
234 $data['properties'][$prop] = $message->getProperty( $prop );
235 ApiResult::setIndexedTagNameRecursive( $data['properties'], 'val' );
236 }
237 }
238
239 return $data;
240 }
241
246 private function getWorkflowState( string $groupId, string $language ) {
247 $dbr = $this->loadBalancer->getConnection( DB_REPLICA );
248 return $dbr->newSelectQueryBuilder()
249 ->select( 'tgr_state' )
250 ->from( 'translate_groupreviews' )
251 ->where( [
252 'tgr_group' => $groupId,
253 'tgr_lang' => $language
254 ] )
255 ->caller( __METHOD__ )
256 ->fetchField();
257 }
258
260 protected function getAllowedParams(): array {
261 return [
262 'group' => [
263 ParamValidator::PARAM_TYPE => 'string',
264 ParamValidator::PARAM_REQUIRED => true,
265 ],
266 'language' => [
267 ParamValidator::PARAM_TYPE => 'string',
268 ParamValidator::PARAM_DEFAULT => 'en',
269 ],
270 'limit' => [
271 ParamValidator::PARAM_DEFAULT => 500,
272 ParamValidator::PARAM_TYPE => 'limit',
273 IntegerDef::PARAM_MIN => 1,
274 IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG2,
275 IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2,
276 ],
277 'offset' => [
278 ParamValidator::PARAM_DEFAULT => '',
279 ParamValidator::PARAM_TYPE => 'string',
280 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
281 ],
282 'filter' => [
283 ParamValidator::PARAM_TYPE => 'string',
284 ParamValidator::PARAM_DEFAULT => '!optional|!ignored',
285 ParamValidator::PARAM_ISMULTI => true,
286 ],
287 'prop' => [
288 ParamValidator::PARAM_TYPE => [
289 'definition',
290 'translation',
291 'tags',
292 'revision',
293 'properties'
294 ],
295 ParamValidator::PARAM_DEFAULT => 'definition|translation',
296 ParamValidator::PARAM_ISMULTI => true,
297 ApiBase::PARAM_HELP_MSG =>
298 [ 'apihelp-query+messagecollection-param-prop', TRANSLATE_FUZZY ],
299 ],
300 ];
301 }
302
304 protected function getExamplesMessages(): array {
305 return [
306 'action=query&meta=siteinfo&siprop=languages'
307 => 'apihelp-query+messagecollection-example-1',
308 'action=query&list=messagecollection&mcgroup=page-Example'
309 => 'apihelp-query+messagecollection-example-2',
310 'action=query&list=messagecollection&mcgroup=page-Example&mclanguage=fi&' .
311 'mcprop=definition|translation|tags&mcfilter=optional'
312 => 'apihelp-query+messagecollection-example-3',
313 'action=query&generator=messagecollection&gmcgroup=page-Example&gmclanguage=nl&prop=revisions'
314 => 'apihelp-query+messagecollection-example-4',
315 ];
316 }
317}
return[ 'Translate:ConfigHelper'=> static function():ConfigHelper { return new ConfigHelper();}, 'Translate:CsvTranslationImporter'=> static function(MediaWikiServices $services):CsvTranslationImporter { return new CsvTranslationImporter( $services->getWikiPageFactory());}, 'Translate:EntitySearch'=> static function(MediaWikiServices $services):EntitySearch { return new EntitySearch($services->getMainWANObjectCache(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), MessageGroups::singleton(), $services->getNamespaceInfo(), $services->get( 'Translate:MessageIndex'), $services->getTitleParser(), $services->getTitleFormatter());}, 'Translate:ExternalMessageSourceStateImporter'=> static function(MediaWikiServices $services):ExternalMessageSourceStateImporter { return new ExternalMessageSourceStateImporter($services->getMainConfig(), $services->get( 'Translate:GroupSynchronizationCache'), $services->getJobQueueGroup(), LoggerFactory::getInstance( 'Translate.GroupSynchronization'), MessageIndex::singleton());}, 'Translate:GroupSynchronizationCache'=> static function(MediaWikiServices $services):GroupSynchronizationCache { return new GroupSynchronizationCache( $services->get( 'Translate:PersistentCache'));}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore(new RevTagStore(), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'));}, 'Translate:MessageGroupReview'=> static function(MediaWikiServices $services):MessageGroupReview { return new MessageGroupReview($services->getDBLoadBalancer(), $services->getHookContainer());}, 'Translate:MessageIndex'=> static function(MediaWikiServices $services):MessageIndex { $params=$services->getMainConfig() ->get( 'TranslateMessageIndex');if(is_string( $params)) { $params=(array) $params;} $class=array_shift( $params);return new $class( $params);}, 'Translate:ParsingPlaceholderFactory'=> static function():ParsingPlaceholderFactory { return new ParsingPlaceholderFactory();}, 'Translate:PersistentCache'=> static function(MediaWikiServices $services):PersistentCache { return new PersistentDatabaseCache($services->getDBLoadBalancer(), $services->getJsonCodec());}, 'Translate:ProgressStatsTableFactory'=> static function(MediaWikiServices $services):ProgressStatsTableFactory { return new ProgressStatsTableFactory($services->getLinkRenderer(), $services->get( 'Translate:ConfigHelper'));}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleFactory'=> static function(MediaWikiServices $services):TranslatableBundleFactory { return new TranslatableBundleFactory($services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:MessageBundleStore'));}, 'Translate:TranslatableBundleMover'=> static function(MediaWikiServices $services):TranslatableBundleMover { return new TranslatableBundleMover($services->getMovePageFactory(), $services->getJobQueueGroup(), $services->getLinkBatchFactory(), $services->get( 'Translate:TranslatableBundleFactory'), $services->get( 'Translate:SubpageListBuilder'), $services->getMainConfig() ->get( 'TranslatePageMoveLimit'));}, 'Translate:TranslatablePageParser'=> static function(MediaWikiServices $services):TranslatablePageParser { return new TranslatablePageParser($services->get( 'Translate:ParsingPlaceholderFactory'));}, 'Translate:TranslatablePageStore'=> static function(MediaWikiServices $services):TranslatablePageStore { return new TranslatablePageStore($services->get( 'Translate:MessageIndex'), $services->getJobQueueGroup(), new RevTagStore(), $services->getDBLoadBalancer());}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { $db=$services->getDBLoadBalancer() ->getConnectionRef(DB_REPLICA);return new TranslationStashStorage( $db);}, 'Translate:TranslationStatsDataProvider'=> static function(MediaWikiServices $services):TranslationStatsDataProvider { return new TranslationStatsDataProvider(new ServiceOptions(TranslationStatsDataProvider::CONSTRUCTOR_OPTIONS, $services->getMainConfig()), $services->getObjectFactory());}, 'Translate:TranslationUnitStoreFactory'=> static function(MediaWikiServices $services):TranslationUnitStoreFactory { return new TranslationUnitStoreFactory( $services->getDBLoadBalancer());}, 'Translate:TranslatorActivity'=> static function(MediaWikiServices $services):TranslatorActivity { $query=new TranslatorActivityQuery($services->getMainConfig(), $services->getDBLoadBalancer());return new TranslatorActivity($services->getMainObjectStash(), $query, $services->getJobQueueGroup());}, 'Translate:TtmServerFactory'=> static function(MediaWikiServices $services):TtmServerFactory { $config=$services->getMainConfig();$default=$config->get( 'TranslateTranslationDefaultService');if( $default===false) { $default=null;} return new TtmServerFactory( $config->get( 'TranslateTranslationServices'), $default);}]
@phpcs-require-sorted-array
A helper class added to work with configuration values of the Translate Extension.
Factory class for accessing message groups individually by id or all of them as an list.
Class for pointing to messages, like Title class is for titles.
Interface for message objects used by MessageCollection.
Definition Message.php:14
definition()
Get the message definition.
Definition Message.php:51
translation()
Get the message translation.
getPropertyNames()
Get all the available property names.
Definition Message.php:122
getTags()
Return all tags for this message;.
Definition Message.php:98
Essentially random collection of helper functions, similar to GlobalFunctions.php.