Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 149 |
|
0.00% |
0 / 13 |
CRAP | |
0.00% |
0 / 1 |
SpecialPageLanguage | |
0.00% |
0 / 148 |
|
0.00% |
0 / 13 |
1056 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
doesWrites | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
preHtml | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getFormFields | |
0.00% |
0 / 45 |
|
0.00% |
0 / 1 |
56 | |||
postHtml | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getDisplayFormat | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
alterForm | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
onSubmit | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
30 | |||
changePageLanguage | |
0.00% |
0 / 59 |
|
0.00% |
0 / 1 |
90 | |||
onSuccess | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
showLogFragment | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
prefixSearchSubpages | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Implements Special:PageLanguage |
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 | * @ingroup SpecialPage |
22 | * @author Kunal Grover |
23 | * @since 1.24 |
24 | */ |
25 | |
26 | namespace MediaWiki\Specials; |
27 | |
28 | use ApiMessage; |
29 | use LogEventsList; |
30 | use LogPage; |
31 | use ManualLogEntry; |
32 | use MediaWiki\Content\IContentHandlerFactory; |
33 | use MediaWiki\Context\IContextSource; |
34 | use MediaWiki\HTMLForm\HTMLForm; |
35 | use MediaWiki\Language\RawMessage; |
36 | use MediaWiki\Languages\LanguageNameUtils; |
37 | use MediaWiki\MainConfigNames; |
38 | use MediaWiki\MediaWikiServices; |
39 | use MediaWiki\Permissions\PermissionStatus; |
40 | use MediaWiki\SpecialPage\FormSpecialPage; |
41 | use MediaWiki\Status\Status; |
42 | use MediaWiki\Title\MalformedTitleException; |
43 | use MediaWiki\Title\Title; |
44 | use SearchEngineFactory; |
45 | use Wikimedia\Rdbms\IConnectionProvider; |
46 | use Wikimedia\Rdbms\IDatabase; |
47 | use Xml; |
48 | |
49 | /** |
50 | * Special page for changing the content language of a page |
51 | * |
52 | * @ingroup SpecialPage |
53 | */ |
54 | class SpecialPageLanguage extends FormSpecialPage { |
55 | /** |
56 | * @var string URL to go to if language change successful |
57 | */ |
58 | private $goToUrl; |
59 | |
60 | private IContentHandlerFactory $contentHandlerFactory; |
61 | private LanguageNameUtils $languageNameUtils; |
62 | private IConnectionProvider $dbProvider; |
63 | private SearchEngineFactory $searchEngineFactory; |
64 | |
65 | /** |
66 | * @param IContentHandlerFactory $contentHandlerFactory |
67 | * @param LanguageNameUtils $languageNameUtils |
68 | * @param IConnectionProvider $dbProvider |
69 | * @param SearchEngineFactory $searchEngineFactory |
70 | */ |
71 | public function __construct( |
72 | IContentHandlerFactory $contentHandlerFactory, |
73 | LanguageNameUtils $languageNameUtils, |
74 | IConnectionProvider $dbProvider, |
75 | SearchEngineFactory $searchEngineFactory |
76 | ) { |
77 | parent::__construct( 'PageLanguage', 'pagelang' ); |
78 | $this->contentHandlerFactory = $contentHandlerFactory; |
79 | $this->languageNameUtils = $languageNameUtils; |
80 | $this->dbProvider = $dbProvider; |
81 | $this->searchEngineFactory = $searchEngineFactory; |
82 | } |
83 | |
84 | public function doesWrites() { |
85 | return true; |
86 | } |
87 | |
88 | protected function preHtml() { |
89 | $this->getOutput()->addModules( 'mediawiki.misc-authed-ooui' ); |
90 | return parent::preHtml(); |
91 | } |
92 | |
93 | protected function getFormFields() { |
94 | // Get default from the subpage of Special page |
95 | $defaultName = $this->par; |
96 | $title = $defaultName ? Title::newFromText( $defaultName ) : null; |
97 | if ( $title ) { |
98 | $defaultPageLanguage = $this->contentHandlerFactory->getContentHandler( $title->getContentModel() ) |
99 | ->getPageLanguage( $title ); |
100 | |
101 | $hasCustomLanguageSet = !$defaultPageLanguage->equals( $title->getPageLanguage() ); |
102 | } else { |
103 | $hasCustomLanguageSet = false; |
104 | } |
105 | |
106 | $page = []; |
107 | $page['pagename'] = [ |
108 | 'type' => 'title', |
109 | 'label-message' => 'pagelang-name', |
110 | 'default' => $title ? $title->getPrefixedText() : $defaultName, |
111 | 'autofocus' => $defaultName === null, |
112 | 'exists' => true, |
113 | ]; |
114 | |
115 | // Options for whether to use the default language or select language |
116 | $selectoptions = [ |
117 | (string)$this->msg( 'pagelang-use-default' )->escaped() => 1, |
118 | (string)$this->msg( 'pagelang-select-lang' )->escaped() => 2, |
119 | ]; |
120 | $page['selectoptions'] = [ |
121 | 'id' => 'mw-pl-options', |
122 | 'type' => 'radio', |
123 | 'options' => $selectoptions, |
124 | 'default' => $hasCustomLanguageSet ? 2 : 1 |
125 | ]; |
126 | |
127 | // Building a language selector |
128 | $userLang = $this->getLanguage()->getCode(); |
129 | $languages = $this->languageNameUtils->getLanguageNames( $userLang, LanguageNameUtils::SUPPORTED ); |
130 | $options = []; |
131 | foreach ( $languages as $code => $name ) { |
132 | $options["$code - $name"] = $code; |
133 | } |
134 | |
135 | $page['language'] = [ |
136 | 'id' => 'mw-pl-languageselector', |
137 | 'cssclass' => 'mw-languageselector', |
138 | 'type' => 'select', |
139 | 'options' => $options, |
140 | 'label-message' => 'pagelang-language', |
141 | 'default' => $title ? |
142 | $title->getPageLanguage()->getCode() : |
143 | $this->getConfig()->get( MainConfigNames::LanguageCode ), |
144 | ]; |
145 | |
146 | // Allow user to enter a comment explaining the change |
147 | $page['reason'] = [ |
148 | 'type' => 'text', |
149 | 'label-message' => 'pagelang-reason' |
150 | ]; |
151 | |
152 | return $page; |
153 | } |
154 | |
155 | protected function postHtml() { |
156 | if ( $this->par ) { |
157 | return $this->showLogFragment( $this->par ); |
158 | } |
159 | return ''; |
160 | } |
161 | |
162 | protected function getDisplayFormat() { |
163 | return 'ooui'; |
164 | } |
165 | |
166 | public function alterForm( HTMLForm $form ) { |
167 | $this->getHookRunner()->onLanguageSelector( $this->getOutput(), 'mw-languageselector' ); |
168 | $form->setSubmitTextMsg( 'pagelang-submit' ); |
169 | } |
170 | |
171 | /** |
172 | * @param array $data |
173 | * @return Status |
174 | */ |
175 | public function onSubmit( array $data ) { |
176 | $pageName = $data['pagename']; |
177 | |
178 | // Check if user wants to use default language |
179 | if ( $data['selectoptions'] == 1 ) { |
180 | $newLanguage = 'default'; |
181 | } else { |
182 | $newLanguage = $data['language']; |
183 | } |
184 | |
185 | try { |
186 | $title = Title::newFromTextThrow( $pageName ); |
187 | } catch ( MalformedTitleException $ex ) { |
188 | return Status::newFatal( $ex->getMessageObject() ); |
189 | } |
190 | |
191 | // Check permissions and make sure the user has permission to edit the page |
192 | $status = PermissionStatus::newEmpty(); |
193 | if ( !$this->getAuthority()->authorizeWrite( 'edit', $title, $status ) ) { |
194 | $wikitext = $this->getOutput()->formatPermissionStatus( $status ); |
195 | // Hack to get our wikitext parsed |
196 | return Status::newFatal( new RawMessage( '$1', [ $wikitext ] ) ); |
197 | } |
198 | |
199 | // Url to redirect to after the operation |
200 | $this->goToUrl = $title->getFullUrlForRedirect( |
201 | $title->isRedirect() ? [ 'redirect' => 'no' ] : [] |
202 | ); |
203 | |
204 | return self::changePageLanguage( |
205 | $this->getContext(), |
206 | $title, |
207 | $newLanguage, |
208 | $data['reason'] ?? '', |
209 | [], |
210 | $this->dbProvider->getPrimaryDatabase() |
211 | ); |
212 | } |
213 | |
214 | /** |
215 | * @since 1.36 Added $dbw parameter |
216 | * |
217 | * @param IContextSource $context |
218 | * @param Title $title |
219 | * @param string $newLanguage Language code |
220 | * @param string $reason Reason for the change |
221 | * @param string[] $tags Change tags to apply to the log entry |
222 | * @param IDatabase|null $dbw |
223 | * @return Status |
224 | */ |
225 | public static function changePageLanguage( IContextSource $context, Title $title, |
226 | $newLanguage, $reason = "", array $tags = [], IDatabase $dbw = null ) { |
227 | // Get the default language for the wiki |
228 | $defLang = $context->getConfig()->get( MainConfigNames::LanguageCode ); |
229 | |
230 | $pageId = $title->getArticleID(); |
231 | |
232 | // Check if article exists |
233 | if ( !$pageId ) { |
234 | return Status::newFatal( |
235 | 'pagelang-nonexistent-page', |
236 | wfEscapeWikiText( $title->getPrefixedText() ) |
237 | ); |
238 | } |
239 | |
240 | // Load the page language from DB |
241 | $dbw ??= MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase(); |
242 | $oldLanguage = $dbw->newSelectQueryBuilder() |
243 | ->select( 'page_lang' ) |
244 | ->from( 'page' ) |
245 | ->where( [ 'page_id' => $pageId ] ) |
246 | ->caller( __METHOD__ )->fetchField(); |
247 | |
248 | // Check if user wants to use the default language |
249 | if ( $newLanguage === 'default' ) { |
250 | $newLanguage = null; |
251 | } |
252 | |
253 | // No change in language |
254 | if ( $newLanguage === $oldLanguage ) { |
255 | // Check if old language does not exist |
256 | if ( !$oldLanguage ) { |
257 | return Status::newFatal( ApiMessage::create( |
258 | [ |
259 | 'pagelang-unchanged-language-default', |
260 | wfEscapeWikiText( $title->getPrefixedText() ) |
261 | ], |
262 | 'pagelang-unchanged-language' |
263 | ) ); |
264 | } |
265 | return Status::newFatal( |
266 | 'pagelang-unchanged-language', |
267 | wfEscapeWikiText( $title->getPrefixedText() ), |
268 | $oldLanguage |
269 | ); |
270 | } |
271 | |
272 | // Hardcoded [def] if the language is set to null |
273 | $logOld = $oldLanguage ?: $defLang . '[def]'; |
274 | $logNew = $newLanguage ?: $defLang . '[def]'; |
275 | |
276 | // Writing new page language to database |
277 | $dbw->newUpdateQueryBuilder() |
278 | ->update( 'page' ) |
279 | ->set( [ 'page_lang' => $newLanguage ] ) |
280 | ->where( [ |
281 | 'page_id' => $pageId, |
282 | 'page_lang' => $oldLanguage, |
283 | ] ) |
284 | ->caller( __METHOD__ )->execute(); |
285 | |
286 | if ( !$dbw->affectedRows() ) { |
287 | return Status::newFatal( 'pagelang-db-failed' ); |
288 | } |
289 | |
290 | // Logging change of language |
291 | $logParams = [ |
292 | '4::oldlanguage' => $logOld, |
293 | '5::newlanguage' => $logNew |
294 | ]; |
295 | $entry = new ManualLogEntry( 'pagelang', 'pagelang' ); |
296 | $entry->setPerformer( $context->getUser() ); |
297 | $entry->setTarget( $title ); |
298 | $entry->setParameters( $logParams ); |
299 | $entry->setComment( is_string( $reason ) ? $reason : "" ); |
300 | $entry->addTags( $tags ); |
301 | |
302 | $logid = $entry->insert(); |
303 | $entry->publish( $logid ); |
304 | |
305 | // Force re-render so that language-based content (parser functions etc.) gets updated |
306 | $title->invalidateCache(); |
307 | |
308 | return Status::newGood( (object)[ |
309 | 'oldLanguage' => $logOld, |
310 | 'newLanguage' => $logNew, |
311 | 'logId' => $logid, |
312 | ] ); |
313 | } |
314 | |
315 | public function onSuccess() { |
316 | // Success causes a redirect |
317 | $this->getOutput()->redirect( $this->goToUrl ); |
318 | } |
319 | |
320 | private function showLogFragment( $title ) { |
321 | $moveLogPage = new LogPage( 'pagelang' ); |
322 | $out1 = Xml::element( 'h2', null, $moveLogPage->getName()->text() ); |
323 | $out2 = ''; |
324 | LogEventsList::showLogExtract( $out2, 'pagelang', $title ); |
325 | return $out1 . $out2; |
326 | } |
327 | |
328 | /** |
329 | * Return an array of subpages beginning with $search that this special page will accept. |
330 | * |
331 | * @param string $search Prefix to search for |
332 | * @param int $limit Maximum number of results to return (usually 10) |
333 | * @param int $offset Number of results to skip (usually 0) |
334 | * @return string[] Matching subpages |
335 | */ |
336 | public function prefixSearchSubpages( $search, $limit, $offset ) { |
337 | return $this->prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory ); |
338 | } |
339 | |
340 | protected function getGroupName() { |
341 | return 'pagetools'; |
342 | } |
343 | } |
344 | |
345 | /** |
346 | * Retain the old class name for backwards compatibility. |
347 | * @deprecated since 1.41 |
348 | */ |
349 | class_alias( SpecialPageLanguage::class, 'SpecialPageLanguage' ); |