Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 142
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiQueryAllMessages
0.00% covered (danger)
0.00%
0 / 141
0.00% covered (danger)
0.00%
0 / 6
1892
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 95
0.00% covered (danger)
0.00%
0 / 1
1332
 getCacheMode
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 getAllowedParams
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
2
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getHelpUrls
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
4 *
5 * @license GPL-2.0-or-later
6 * @file
7 */
8
9namespace MediaWiki\Api;
10
11use MediaWiki\Language\Language;
12use MediaWiki\Language\LanguageFactory;
13use MediaWiki\Language\LanguageNameUtils;
14use MediaWiki\Language\LocalisationCache;
15use MediaWiki\Language\MessageCache;
16use MediaWiki\Specials\Pager\AllMessagesTablePager;
17use MediaWiki\Title\Title;
18use Wikimedia\ParamValidator\ParamValidator;
19
20/**
21 * A query action to return messages from site message cache
22 *
23 * @ingroup API
24 */
25class ApiQueryAllMessages extends ApiQueryBase {
26
27    public function __construct(
28        ApiQuery $query,
29        string $moduleName,
30        private readonly Language $contentLanguage,
31        private readonly LanguageFactory $languageFactory,
32        private readonly LanguageNameUtils $languageNameUtils,
33        private readonly LocalisationCache $localisationCache,
34        private readonly MessageCache $messageCache,
35    ) {
36        parent::__construct( $query, $moduleName, 'am' );
37    }
38
39    public function execute() {
40        $params = $this->extractRequestParams();
41        if ( $params['lang'] === null ) {
42            $langObj = $this->getLanguage();
43        } elseif ( !$this->languageNameUtils->isValidCode( $params['lang'] ) ) {
44            $this->dieWithError(
45                [ 'apierror-invalidlang', $this->encodeParamName( 'lang' ) ], 'invalidlang'
46            );
47        } else {
48            $langObj = $this->languageFactory->getLanguage( $params['lang'] );
49        }
50
51        if ( $params['enableparser'] ) {
52            if ( $params['title'] !== null ) {
53                $title = Title::newFromText( $params['title'] );
54                if ( !$title || $title->isExternal() ) {
55                    $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
56                }
57            } else {
58                $title = Title::newFromText( 'API' );
59            }
60        }
61
62        $prop = array_fill_keys( (array)$params['prop'], true );
63
64        // Determine which messages should we print
65        if ( in_array( '*', $params['messages'] ) ) {
66            $message_names = $this->localisationCache->getSubitemList( $langObj->getCode(), 'messages' ) ?? [];
67            if ( $params['includelocal'] ) {
68                $message_names = array_unique( array_merge(
69                    $message_names,
70                    // Pass in the content language code so we get local messages that have a
71                    // MediaWiki:msgkey page. We might theoretically miss messages that have no
72                    // MediaWiki:msgkey page but do have a MediaWiki:msgkey/lang page, but that's
73                    // just a stupid case.
74                    $this->messageCache->getAllMessageKeys( $this->contentLanguage->getCode() )
75                ) );
76            }
77            sort( $message_names );
78            $messages_target = $message_names;
79        } else {
80            $messages_target = $params['messages'];
81        }
82
83        // Filter messages that have the specified prefix
84        // Because we sorted the message array earlier, they will appear in a clump:
85        if ( isset( $params['prefix'] ) ) {
86            $skip = false;
87            $messages_filtered = [];
88            foreach ( $messages_target as $message ) {
89                if ( str_starts_with( $message, $params['prefix'] ) ) {
90                    if ( !$skip ) {
91                        $skip = true;
92                    }
93                    $messages_filtered[] = $message;
94                } elseif ( $skip ) {
95                    break;
96                }
97            }
98            $messages_target = $messages_filtered;
99        }
100
101        // Filter messages that contain specified string
102        if ( isset( $params['filter'] ) ) {
103            $messages_filtered = [];
104            foreach ( $messages_target as $message ) {
105                if ( str_contains( $message, $params['filter'] ) ) {
106                    $messages_filtered[] = $message;
107                }
108            }
109            $messages_target = $messages_filtered;
110        }
111
112        // Whether we have any sort of message customisation filtering
113        $customiseFilterEnabled = $params['customised'] !== 'all';
114        if ( $customiseFilterEnabled ) {
115            $customisedMessages = AllMessagesTablePager::getCustomisedStatuses(
116                array_map(
117                    $langObj->ucfirst( ... ),
118                    $messages_target
119                ),
120                $langObj->getCode(),
121                !$langObj->equals( $this->contentLanguage ),
122                $this->getDB()
123            );
124
125            $customised = $params['customised'] === 'modified';
126        }
127
128        // Get all requested messages and print the result
129        $skip = $params['from'] !== null;
130        $useto = $params['to'] !== null;
131        $result = $this->getResult();
132        foreach ( $messages_target as $message ) {
133            // Skip all messages up to $params['from']
134            if ( $skip && $message === $params['from'] ) {
135                $skip = false;
136            }
137
138            if ( $useto && $message > $params['to'] ) {
139                break;
140            }
141
142            if ( !$skip ) {
143                $a = [
144                    'name' => $message,
145                    'normalizedname' => $this->messageCache->normalizeKey( $message ),
146                ];
147
148                $args = [];
149                if ( isset( $params['args'] ) && count( $params['args'] ) != 0 ) {
150                    $args = $params['args'];
151                }
152
153                if ( $customiseFilterEnabled ) {
154                    $messageIsCustomised = isset( $customisedMessages['pages'][$langObj->ucfirst( $message )] );
155                    // @phan-suppress-next-line PhanPossiblyUndeclaredVariable customised is set when used
156                    if ( $customised === $messageIsCustomised ) {
157                        // @phan-suppress-next-line PhanPossiblyUndeclaredVariable customised is set when used
158                        if ( $customised ) {
159                            $a['customised'] = true;
160                        }
161                    } else {
162                        continue;
163                    }
164                }
165
166                $msg = $this->msg( $message, $args )->inLanguage( $langObj );
167
168                if ( !$msg->exists() ) {
169                    $a['missing'] = true;
170                } else {
171                    // Check if the parser is enabled:
172                    if ( $params['enableparser'] ) {
173                        // @phan-suppress-next-line PhanPossiblyUndeclaredVariable title is set when used
174                        $msgString = $msg->page( $title )->text();
175                    } else {
176                        $msgString = $msg->plain();
177                    }
178                    if ( !$params['nocontent'] ) {
179                        ApiResult::setContentValue( $a, 'content', $msgString );
180                    }
181                    if ( isset( $prop['default'] ) ) {
182                        $default = $this->msg( $message )->inLanguage( $langObj )->useDatabase( false );
183                        if ( !$default->exists() ) {
184                            $a['defaultmissing'] = true;
185                        } elseif ( $default->plain() != $msgString ) {
186                            $a['default'] = $default->plain();
187                        }
188                    }
189                }
190                $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $a );
191                if ( !$fit ) {
192                    $this->setContinueEnumParameter( 'from', $message );
193                    break;
194                }
195            }
196        }
197        $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'message' );
198    }
199
200    /** @inheritDoc */
201    public function getCacheMode( $params ) {
202        if ( $params['lang'] === null ) {
203            // Language not specified, will be fetched from preferences
204            return 'anon-public-user-private';
205        } elseif ( $params['enableparser'] ) {
206            // User-specific parser options will be used
207            return 'anon-public-user-private';
208        } else {
209            // OK to cache
210            return 'public';
211        }
212    }
213
214    /** @inheritDoc */
215    public function getAllowedParams() {
216        return [
217            'messages' => [
218                ParamValidator::PARAM_DEFAULT => '*',
219                ParamValidator::PARAM_ISMULTI => true,
220            ],
221            'prop' => [
222                ParamValidator::PARAM_ISMULTI => true,
223                ParamValidator::PARAM_TYPE => [
224                    'default'
225                ]
226            ],
227            'enableparser' => false,
228            'nocontent' => false,
229            'includelocal' => false,
230            'args' => [
231                ParamValidator::PARAM_ISMULTI => true,
232                ParamValidator::PARAM_ALLOW_DUPLICATES => true,
233            ],
234            'filter' => [],
235            'customised' => [
236                ParamValidator::PARAM_DEFAULT => 'all',
237                ParamValidator::PARAM_TYPE => [
238                    'all',
239                    'modified',
240                    'unmodified'
241                ]
242            ],
243            'lang' => null,
244            'from' => null,
245            'to' => null,
246            'title' => null,
247            'prefix' => null,
248        ];
249    }
250
251    /** @inheritDoc */
252    protected function getExamplesMessages() {
253        return [
254            'action=query&meta=allmessages&amprefix=ipb-'
255                => 'apihelp-query+allmessages-example-ipb',
256            'action=query&meta=allmessages&ammessages=august|mainpage&amlang=de'
257                => 'apihelp-query+allmessages-example-de',
258        ];
259    }
260
261    /** @inheritDoc */
262    public function getHelpUrls() {
263        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Allmessages';
264    }
265}
266
267/** @deprecated class alias since 1.43 */
268class_alias( ApiQueryAllMessages::class, 'ApiQueryAllMessages' );