1<?php
2/**
3 * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 */
22
23use MediaWiki\Languages\LanguageFactory;
24use MediaWiki\Languages\LanguageNameUtils;
25
26/**
27 * A query action to return messages from site message cache
28 *
29 * @ingroup API
30 */
31class ApiQueryAllMessages extends ApiQueryBase {
32
33	/** @var Language */
34	private $contentLanguage;
35
36	/** @var LanguageFactory */
37	private $languageFactory;
38
39	/** @var LanguageNameUtils */
40	private $languageNameUtils;
41
42	/** @var LocalisationCache */
43	private $localisationCache;
44
45	/** @var MessageCache */
46	private $messageCache;
47
48	/**
49	 * @param ApiQuery $query
50	 * @param string $moduleName
51	 * @param Language $contentLanguage
52	 * @param LanguageFactory $languageFactory
53	 * @param LanguageNameUtils $languageNameUtils
54	 * @param LocalisationCache $localisationCache
55	 * @param MessageCache $messageCache
56	 */
57	public function __construct(
58		ApiQuery $query,
59		$moduleName,
60		Language $contentLanguage,
61		LanguageFactory $languageFactory,
62		LanguageNameUtils $languageNameUtils,
63		LocalisationCache $localisationCache,
64		MessageCache $messageCache
65	) {
66		parent::__construct( $query, $moduleName, 'am' );
67		$this->contentLanguage = $contentLanguage;
68		$this->languageFactory = $languageFactory;
69		$this->languageNameUtils = $languageNameUtils;
70		$this->localisationCache = $localisationCache;
71		$this->messageCache = $messageCache;
72	}
73
74	public function execute() {
75		$params = $this->extractRequestParams();
76		if ( $params['lang'] === null ) {
77			$langObj = $this->getLanguage();
78		} elseif ( !$this->languageNameUtils->isValidCode( $params['lang'] ) ) {
79			$this->dieWithError(
80				[ 'apierror-invalidlang', $this->encodeParamName( 'lang' ) ], 'invalidlang'
81			);
82		} else {
83			$langObj = $this->languageFactory->getLanguage( $params['lang'] );
84		}
85
86		if ( $params['enableparser'] ) {
87			if ( $params['title'] !== null ) {
88				$title = Title::newFromText( $params['title'] );
89				if ( !$title || $title->isExternal() ) {
90					$this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
91				}
92			} else {
93				$title = Title::newFromText( 'API' );
94			}
95		}
96
97		$prop = array_fill_keys( (array)$params['prop'], true );
98
99		// Determine which messages should we print
100		if ( in_array( '*', $params['messages'] ) ) {
101			$message_names = $this->localisationCache->getSubitemList( $langObj->getCode(), 'messages' );
102			if ( $params['includelocal'] ) {
103				$message_names = array_unique( array_merge(
104					$message_names,
105					// Pass in the content language code so we get local messages that have a
106					// MediaWiki:msgkey page. We might theoretically miss messages that have no
107					// MediaWiki:msgkey page but do have a MediaWiki:msgkey/lang page, but that's
108					// just a stupid case.
109					$this->messageCache->getAllMessageKeys( $this->contentLanguage->getCode() )
110				) );
111			}
112			sort( $message_names );
113			$messages_target = $message_names;
114		} else {
115			$messages_target = $params['messages'];
116		}
117
118		// Filter messages that have the specified prefix
119		// Because we sorted the message array earlier, they will appear in a clump:
120		if ( isset( $params['prefix'] ) ) {
121			$skip = false;
122			$messages_filtered = [];
123			foreach ( $messages_target as $message ) {
124				// === 0: must be at beginning of string (position 0)
125				if ( strpos( $message, $params['prefix'] ) === 0 ) {
126					if ( !$skip ) {
127						$skip = true;
128					}
129					$messages_filtered[] = $message;
130				} elseif ( $skip ) {
131					break;
132				}
133			}
134			$messages_target = $messages_filtered;
135		}
136
137		// Filter messages that contain specified string
138		if ( isset( $params['filter'] ) ) {
139			$messages_filtered = [];
140			foreach ( $messages_target as $message ) {
141				// !== is used because filter can be at the beginning of the string
142				if ( strpos( $message, $params['filter'] ) !== false ) {
143					$messages_filtered[] = $message;
144				}
145			}
146			$messages_target = $messages_filtered;
147		}
148
149		// Whether we have any sort of message customisation filtering
150		$customiseFilterEnabled = $params['customised'] !== 'all';
151		if ( $customiseFilterEnabled ) {
152			$customisedMessages = AllMessagesTablePager::getCustomisedStatuses(
153				array_map(
154					[ $langObj, 'ucfirst' ],
155					$messages_target
156				),
157				$langObj->getCode(),
158				!$langObj->equals( $this->contentLanguage ),
159				$this->getDB()
160			);
161
162			$customised = $params['customised'] === 'modified';
163		}
164
165		// Get all requested messages and print the result
166		$skip = $params['from'] !== null;
167		$useto = $params['to'] !== null;
168		$result = $this->getResult();
169		foreach ( $messages_target as $message ) {
170			// Skip all messages up to $params['from']
171			if ( $skip && $message === $params['from'] ) {
172				$skip = false;
173			}
174
175			if ( $useto && $message > $params['to'] ) {
176				break;
177			}
178
179			if ( !$skip ) {
180				$a = [
181					'name' => $message,
182					'normalizedname' => MessageCache::normalizeKey( $message ),
183				];
184
185				$args = [];
186				if ( isset( $params['args'] ) && count( $params['args'] ) != 0 ) {
187					$args = $params['args'];
188				}
189
190				if ( $customiseFilterEnabled ) {
191					$messageIsCustomised = isset( $customisedMessages['pages'][$langObj->ucfirst( $message )] );
192					if ( $customised === $messageIsCustomised ) {
193						if ( $customised ) {
194							$a['customised'] = true;
195						}
196					} else {
197						continue;
198					}
199				}
200
201				$msg = wfMessage( $message, $args )->inLanguage( $langObj );
202
203				if ( !$msg->exists() ) {
204					$a['missing'] = true;
205				} else {
206					// Check if the parser is enabled:
207					if ( $params['enableparser'] ) {
208						$msgString = $msg->page( $title )->text();
209					} else {
210						$msgString = $msg->plain();
211					}
212					if ( !$params['nocontent'] ) {
213						ApiResult::setContentValue( $a, 'content', $msgString );
214					}
215					if ( isset( $prop['default'] ) ) {
216						$default = wfMessage( $message )->inLanguage( $langObj )->useDatabase( false );
217						if ( !$default->exists() ) {
218							$a['defaultmissing'] = true;
219						} elseif ( $default->plain() != $msgString ) {
220							$a['default'] = $default->plain();
221						}
222					}
223				}
224				$fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $a );
225				if ( !$fit ) {
226					$this->setContinueEnumParameter( 'from', $message );
227					break;
228				}
229			}
230		}
231		$result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'message' );
232	}
233
234	public function getCacheMode( $params ) {
235		if ( $params['lang'] === null ) {
236			// Language not specified, will be fetched from preferences
237			return 'anon-public-user-private';
238		} elseif ( $params['enableparser'] ) {
239			// User-specific parser options will be used
240			return 'anon-public-user-private';
241		} else {
242			// OK to cache
243			return 'public';
244		}
245	}
246
247	public function getAllowedParams() {
248		return [
249			'messages' => [
250				ApiBase::PARAM_DFLT => '*',
251				ApiBase::PARAM_ISMULTI => true,
252			],
253			'prop' => [
254				ApiBase::PARAM_ISMULTI => true,
255				ApiBase::PARAM_TYPE => [
256					'default'
257				]
258			],
259			'enableparser' => false,
260			'nocontent' => false,
261			'includelocal' => false,
262			'args' => [
263				ApiBase::PARAM_ISMULTI => true,
264				ApiBase::PARAM_ALLOW_DUPLICATES => true,
265			],
266			'filter' => [],
267			'customised' => [
268				ApiBase::PARAM_DFLT => 'all',
269				ApiBase::PARAM_TYPE => [
270					'all',
271					'modified',
272					'unmodified'
273				]
274			],
275			'lang' => null,
276			'from' => null,
277			'to' => null,
278			'title' => null,
279			'prefix' => null,
280		];
281	}
282
283	protected function getExamplesMessages() {
284		return [
285			'action=query&meta=allmessages&amprefix=ipb-'
286				=> 'apihelp-query+allmessages-example-ipb',
287			'action=query&meta=allmessages&ammessages=august|mainpage&amlang=de'
288				=> 'apihelp-query+allmessages-example-de',
289		];
290	}
291
292	public function getHelpUrls() {
293		return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Allmessages';
294	}
295}
296