Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
83.33% |
40 / 48 |
|
50.00% |
3 / 6 |
CRAP | |
0.00% |
0 / 1 |
SpecialMyLanguage | |
85.11% |
40 / 47 |
|
50.00% |
3 / 6 |
30.59 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getRedirect | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
findTitle | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
findTitleForTransclusion | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
findTitleInternal | |
94.59% |
35 / 37 |
|
0.00% |
0 / 1 |
22.08 | |||
personallyIdentifiableTarget | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Implements Special:MyLanguage |
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 | * @author Niklas Laxström |
22 | * @author Siebrand Mazeland |
23 | * @copyright Copyright © 2010-2013 Niklas Laxström, Siebrand Mazeland |
24 | */ |
25 | |
26 | namespace MediaWiki\Specials; |
27 | |
28 | use MediaWiki\Languages\LanguageNameUtils; |
29 | use MediaWiki\Page\RedirectLookup; |
30 | use MediaWiki\SpecialPage\RedirectSpecialArticle; |
31 | use MediaWiki\Title\Title; |
32 | |
33 | /** |
34 | * Unlisted special page which redirects the user to the appropriate translated version of |
35 | * a page if it exists. |
36 | * |
37 | * Usage: [[Special:MyLanguage/Page name|link text]] |
38 | * |
39 | * @since 1.24 |
40 | * @ingroup SpecialPage |
41 | */ |
42 | class SpecialMyLanguage extends RedirectSpecialArticle { |
43 | |
44 | private LanguageNameUtils $languageNameUtils; |
45 | private RedirectLookup $redirectLookup; |
46 | |
47 | /** |
48 | * @param LanguageNameUtils $languageNameUtils |
49 | * @param RedirectLookup $redirectLookup |
50 | */ |
51 | public function __construct( |
52 | LanguageNameUtils $languageNameUtils, |
53 | RedirectLookup $redirectLookup |
54 | ) { |
55 | parent::__construct( 'MyLanguage' ); |
56 | $this->languageNameUtils = $languageNameUtils; |
57 | $this->redirectLookup = $redirectLookup; |
58 | } |
59 | |
60 | /** |
61 | * If the special page is a redirect, then get the Title object it redirects to. |
62 | * False otherwise. |
63 | * |
64 | * @param string|null $subpage |
65 | * @return Title |
66 | */ |
67 | public function getRedirect( $subpage ) { |
68 | $title = $this->findTitle( $subpage ); |
69 | // Go to the main page if given invalid title. |
70 | if ( !$title ) { |
71 | $title = Title::newMainPage(); |
72 | } |
73 | return $title; |
74 | } |
75 | |
76 | /** |
77 | * Find a title. |
78 | * |
79 | * This may return the base page, e.g. if the UI and |
80 | * content language are the same. |
81 | * |
82 | * Examples, assuming the UI language is fi and the content language |
83 | * is en: |
84 | * - input Page: returns Page/fi if it exists, otherwise Page |
85 | * - input Page/de: returns Page/fi if it exists, otherwise Page/de |
86 | * if it exists, otherwise Page |
87 | * |
88 | * @param string|null $subpage |
89 | * @return Title|null |
90 | */ |
91 | public function findTitle( $subpage ) { |
92 | return $this->findTitleInternal( $subpage, false ); |
93 | } |
94 | |
95 | /** |
96 | * Find a title for transclusion. This avoids returning the base |
97 | * page if a suitable alternative exists. |
98 | * |
99 | * Examples, assuming the UI language is fi and the content language |
100 | * is en: |
101 | * - input Page: returns Page/fi if it exists, otherwise Page/en if |
102 | * it exists, otherwise Page |
103 | * - input Page/de: returns Page/fi if it exists, otherwise Page/de |
104 | * if it exists, otherwise Page/en if it exists, otherwise Page |
105 | * |
106 | * @param string|null $subpage |
107 | * @return Title|null |
108 | */ |
109 | public function findTitleForTransclusion( $subpage ) { |
110 | return $this->findTitleInternal( $subpage, true ); |
111 | } |
112 | |
113 | /** |
114 | * Find a title, depending on the content language and the user's |
115 | * interface language. |
116 | * |
117 | * @param string|null $subpage |
118 | * @param bool $forTransclusion |
119 | * @return Title|null |
120 | */ |
121 | private function findTitleInternal( $subpage, $forTransclusion ) { |
122 | // base = title without the language code suffix |
123 | // provided = the title as it was given |
124 | $base = $provided = null; |
125 | if ( $subpage !== null ) { |
126 | $provided = Title::newFromText( $subpage ); |
127 | $base = $provided; |
128 | |
129 | if ( $provided && str_contains( $subpage, '/' ) ) { |
130 | $pos = strrpos( $subpage, '/' ); |
131 | $basepage = substr( $subpage, 0, $pos ); |
132 | $code = substr( $subpage, $pos + 1 ); |
133 | if ( strlen( $code ) && $this->languageNameUtils->isKnownLanguageTag( $code ) ) { |
134 | $base = Title::newFromText( $basepage ); |
135 | } |
136 | } |
137 | } |
138 | |
139 | if ( !$base || !$base->canExist() ) { |
140 | // No subpage provided or base page does not exist |
141 | return null; |
142 | } |
143 | |
144 | $fragment = ''; |
145 | if ( $base->isRedirect() ) { |
146 | $base = $this->redirectLookup->getRedirectTarget( $base ); |
147 | // Preserve the fragment from the redirect target |
148 | $fragment = $base->getFragment(); |
149 | } |
150 | |
151 | $uiLang = $this->getLanguage(); |
152 | $baseLang = $base->getPageLanguage(); |
153 | |
154 | // T309329: Always use subpages for transclusion |
155 | if ( !$forTransclusion && $baseLang->equals( $uiLang ) ) { |
156 | // Short circuit when the current UI language is the |
157 | // page's content language to avoid unnecessary page lookups. |
158 | return $base; |
159 | } |
160 | |
161 | // Check for a subpage in the current UI language |
162 | $proposed = $base->getSubpage( $uiLang->getCode() ); |
163 | if ( $proposed && $proposed->exists() ) { |
164 | if ( $fragment !== '' ) { |
165 | $proposed->setFragment( $fragment ); |
166 | } |
167 | return $proposed; |
168 | } |
169 | |
170 | // Explicit language code given and the page exists |
171 | if ( $provided !== $base && $provided->exists() ) { |
172 | // Not based on the redirect target, don't need the fragment |
173 | return $provided; |
174 | } |
175 | |
176 | // Check for fallback languages specified by the UI language |
177 | $possibilities = $uiLang->getFallbackLanguages(); |
178 | foreach ( $possibilities as $lang ) { |
179 | // $base already include fragments |
180 | // T309329: Always use subpages for transclusion |
181 | // T333187: Do not ignore base language page if matched |
182 | if ( !$forTransclusion && $lang === $baseLang->getCode() ) { |
183 | return $base; |
184 | } |
185 | // Look for subpages if is for transclusion or didn't match base page language |
186 | $proposed = $base->getSubpage( $lang ); |
187 | if ( $proposed && $proposed->exists() ) { |
188 | if ( $fragment !== '' ) { |
189 | $proposed->setFragment( $fragment ); |
190 | } |
191 | return $proposed; |
192 | } |
193 | } |
194 | |
195 | // When all else has failed, return the base page |
196 | return $base; |
197 | } |
198 | |
199 | /** |
200 | * Target can identify a specific user's language preference. |
201 | * |
202 | * @see T109724 |
203 | * @since 1.27 |
204 | * @return bool |
205 | */ |
206 | public function personallyIdentifiableTarget() { |
207 | return true; |
208 | } |
209 | } |
210 | |
211 | /** |
212 | * Retain the old class name for backwards compatibility. |
213 | * @deprecated since 1.41 |
214 | */ |
215 | class_alias( SpecialMyLanguage::class, 'SpecialMyLanguage' ); |