Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 149
0.00% covered (danger)
0.00%
0 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialPageLanguage
0.00% covered (danger)
0.00%
0 / 148
0.00% covered (danger)
0.00%
0 / 13
1056
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 doesWrites
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 preHtml
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getFormFields
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 1
56
 postHtml
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getDisplayFormat
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 alterForm
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 onSubmit
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
30
 changePageLanguage
0.00% covered (danger)
0.00%
0 / 59
0.00% covered (danger)
0.00%
0 / 1
90
 onSuccess
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 showLogFragment
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 prefixSearchSubpages
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
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
26namespace MediaWiki\Specials;
27
28use ApiMessage;
29use LogEventsList;
30use LogPage;
31use ManualLogEntry;
32use MediaWiki\Content\IContentHandlerFactory;
33use MediaWiki\Context\IContextSource;
34use MediaWiki\HTMLForm\HTMLForm;
35use MediaWiki\Language\RawMessage;
36use MediaWiki\Languages\LanguageNameUtils;
37use MediaWiki\MainConfigNames;
38use MediaWiki\MediaWikiServices;
39use MediaWiki\Permissions\PermissionStatus;
40use MediaWiki\SpecialPage\FormSpecialPage;
41use MediaWiki\Status\Status;
42use MediaWiki\Title\MalformedTitleException;
43use MediaWiki\Title\Title;
44use SearchEngineFactory;
45use Wikimedia\Rdbms\IConnectionProvider;
46use Wikimedia\Rdbms\IDatabase;
47use Xml;
48
49/**
50 * Special page for changing the content language of a page
51 *
52 * @ingroup SpecialPage
53 */
54class 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 */
349class_alias( SpecialPageLanguage::class, 'SpecialPageLanguage' );