Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
37.50% |
33 / 88 |
|
0.00% |
0 / 9 |
CRAP | |
0.00% |
0 / 1 |
MinervaPagePermissions | |
37.50% |
33 / 88 |
|
0.00% |
0 / 9 |
610.50 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
setContext | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 | |||
isAllowed | |
86.84% |
33 / 38 |
|
0.00% |
0 / 1 |
24.21 | |||
isTalkAllowed | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isCurrentPageContentModelEditable | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
30 | |||
canEditOrCreate | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
56 | |||
canMove | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
canDelete | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
canProtect | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | * |
18 | * @file |
19 | */ |
20 | namespace MediaWiki\Minerva\Permissions; |
21 | |
22 | use ContentHandler; |
23 | use IContextSource; |
24 | use MediaWiki\Config\Config; |
25 | use MediaWiki\Config\ConfigException; |
26 | use MediaWiki\Content\IContentHandlerFactory; |
27 | use MediaWiki\MainConfigNames; |
28 | use MediaWiki\Minerva\LanguagesHelper; |
29 | use MediaWiki\Minerva\SkinOptions; |
30 | use MediaWiki\Output\OutputPage; |
31 | use MediaWiki\Permissions\Authority; |
32 | use MediaWiki\Permissions\PermissionManager; |
33 | use MediaWiki\Title\Title; |
34 | use MediaWiki\User\UserFactory; |
35 | use MediaWiki\Watchlist\WatchlistManager; |
36 | |
37 | /** |
38 | * A wrapper for all available Minerva permissions. |
39 | */ |
40 | final class MinervaPagePermissions implements IMinervaPagePermissions { |
41 | /** |
42 | * @var Title Current page title |
43 | */ |
44 | private $title; |
45 | /** |
46 | * @var Config Extension config |
47 | */ |
48 | private $config; |
49 | |
50 | /** |
51 | * @var Authority |
52 | */ |
53 | private $performer; |
54 | |
55 | private OutputPage $out; |
56 | |
57 | /** |
58 | * @var ContentHandler |
59 | */ |
60 | private $contentHandler; |
61 | |
62 | /** |
63 | * @var SkinOptions Minerva skin options |
64 | */ |
65 | private $skinOptions; |
66 | |
67 | /** |
68 | * @var LanguagesHelper |
69 | */ |
70 | private $languagesHelper; |
71 | |
72 | /** |
73 | * @var PermissionManager |
74 | */ |
75 | private $permissionManager; |
76 | |
77 | /** |
78 | * @var IContentHandlerFactory |
79 | */ |
80 | private $contentHandlerFactory; |
81 | |
82 | private UserFactory $userFactory; |
83 | |
84 | private WatchlistManager $watchlistManager; |
85 | |
86 | /** |
87 | * Initialize internal Minerva Permissions system |
88 | * @param SkinOptions $skinOptions |
89 | * @param LanguagesHelper $languagesHelper |
90 | * @param PermissionManager $permissionManager |
91 | * @param IContentHandlerFactory $contentHandlerFactory |
92 | * @param UserFactory $userFactory |
93 | * @param WatchlistManager $watchlistManager |
94 | */ |
95 | public function __construct( |
96 | SkinOptions $skinOptions, |
97 | LanguagesHelper $languagesHelper, |
98 | PermissionManager $permissionManager, |
99 | IContentHandlerFactory $contentHandlerFactory, |
100 | UserFactory $userFactory, |
101 | WatchlistManager $watchlistManager |
102 | ) { |
103 | $this->skinOptions = $skinOptions; |
104 | $this->languagesHelper = $languagesHelper; |
105 | $this->permissionManager = $permissionManager; |
106 | $this->contentHandlerFactory = $contentHandlerFactory; |
107 | $this->userFactory = $userFactory; |
108 | $this->watchlistManager = $watchlistManager; |
109 | } |
110 | |
111 | /** |
112 | * @param IContextSource $context |
113 | * @return $this |
114 | */ |
115 | public function setContext( IContextSource $context ) { |
116 | $this->title = $context->getTitle(); |
117 | $this->config = $context->getConfig(); |
118 | $this->performer = $context->getAuthority(); |
119 | $this->out = $context->getOutput(); |
120 | // Title may be undefined in certain contexts (T179833) |
121 | // TODO: Check if this is still true if we always pass a context instead of using global one |
122 | if ( $this->title ) { |
123 | $this->contentHandler = $this->contentHandlerFactory->getContentHandler( |
124 | $this->title->getContentModel() |
125 | ); |
126 | } |
127 | return $this; |
128 | } |
129 | |
130 | /** |
131 | * Gets whether or not the action is allowed. |
132 | * |
133 | * Actions isn't allowed when: |
134 | * <ul> |
135 | * <li>the user is on the main page</li> |
136 | * </ul> |
137 | * |
138 | * The "edit" action is not allowed if editing is not possible on the page |
139 | * @see method isCurrentPageContentModelEditable |
140 | * |
141 | * The "switch-language" is allowed if there are interlanguage links on the page, |
142 | * or <code>$wgMinervaAlwaysShowLanguageButton</code> is truthy. |
143 | * |
144 | * @inheritDoc |
145 | * @throws ConfigException |
146 | */ |
147 | public function isAllowed( $action ) { |
148 | if ( !$this->title ) { |
149 | return false; |
150 | } |
151 | |
152 | // T206406: Enable "Talk" or "Discussion" button on Main page, also, not forgetting |
153 | // the "switch-language" button. But disable "edit" and "watch" actions. |
154 | if ( $this->title->isMainPage() ) { |
155 | if ( $action === self::SWITCH_LANGUAGE ) { |
156 | return !$this->config->get( MainConfigNames::HideInterlanguageLinks ); |
157 | } |
158 | // Only the talk page is allowed on the main page provided user is registered. |
159 | // talk page permission is disabled on mobile for anons |
160 | // https://phabricator.wikimedia.org/T54165 |
161 | return $action === self::TALK && $this->performer->isRegistered(); |
162 | } |
163 | |
164 | if ( $action === self::TALK ) { |
165 | return ( |
166 | $this->title->isTalkPage() || |
167 | $this->title->canHaveTalkPage() |
168 | ); |
169 | } |
170 | |
171 | if ( $action === self::HISTORY && $this->title->exists() ) { |
172 | return $this->skinOptions->get( SkinOptions::HISTORY_IN_PAGE_ACTIONS ); |
173 | } |
174 | |
175 | if ( $action === SkinOptions::TOOLBAR_SUBMENU ) { |
176 | return $this->skinOptions->get( SkinOptions::TOOLBAR_SUBMENU ); |
177 | } |
178 | |
179 | if ( $action === self::EDIT_OR_CREATE ) { |
180 | return $this->canEditOrCreate(); |
181 | } |
182 | |
183 | if ( $action === self::CONTENT_EDIT ) { |
184 | return $this->isCurrentPageContentModelEditable(); |
185 | } |
186 | |
187 | if ( $action === self::WATCHABLE || $action === self::WATCH ) { |
188 | $isWatchable = $this->watchlistManager->isWatchable( $this->title ); |
189 | |
190 | if ( $action === self::WATCHABLE ) { |
191 | return $isWatchable; |
192 | } |
193 | if ( $action === self::WATCH ) { |
194 | return $isWatchable && |
195 | $this->performer->isAllowedAll( 'viewmywatchlist', 'editmywatchlist' ); |
196 | } |
197 | } |
198 | |
199 | if ( $action === self::SWITCH_LANGUAGE ) { |
200 | if ( $this->config->get( MainConfigNames::HideInterlanguageLinks ) ) { |
201 | return false; |
202 | } |
203 | return $this->languagesHelper->doesTitleHasLanguagesOrVariants( $this->out, $this->title ) || |
204 | $this->config->get( 'MinervaAlwaysShowLanguageButton' ); |
205 | } |
206 | |
207 | if ( $action === self::MOVE ) { |
208 | return $this->canMove(); |
209 | } |
210 | |
211 | if ( $action === self::DELETE ) { |
212 | return $this->canDelete(); |
213 | } |
214 | |
215 | if ( $action === self::PROTECT ) { |
216 | return $this->canProtect(); |
217 | } |
218 | |
219 | // Unknown action has been passed. |
220 | return false; |
221 | } |
222 | |
223 | /** |
224 | * @inheritDoc |
225 | */ |
226 | public function isTalkAllowed() { |
227 | return $this->isAllowed( self::TALK ); |
228 | } |
229 | |
230 | /** |
231 | * Checks whether the editor can handle the existing content handler type. |
232 | * |
233 | * @return bool |
234 | */ |
235 | protected function isCurrentPageContentModelEditable() { |
236 | if ( !$this->contentHandler ) { |
237 | return false; |
238 | } |
239 | |
240 | if ( |
241 | $this->contentHandler->supportsDirectEditing() && |
242 | $this->contentHandler->supportsDirectApiEditing() |
243 | ) { |
244 | return true; |
245 | } |
246 | |
247 | // For content types with custom action=edit handlers, let them do their thing |
248 | if ( array_key_exists( 'edit', $this->contentHandler->getActionOverrides() ?? [] ) ) { |
249 | return true; |
250 | } |
251 | |
252 | return false; |
253 | } |
254 | |
255 | /** |
256 | * Returns true if $title page exists and is editable or is creatable by $user as determined by |
257 | * quick checks. |
258 | * @return bool |
259 | */ |
260 | private function canEditOrCreate() { |
261 | if ( !$this->title ) { |
262 | return false; |
263 | } |
264 | |
265 | $userQuickEditCheck = |
266 | $this->performer->probablyCan( 'edit', $this->title ) && ( |
267 | $this->title->exists() || |
268 | $this->performer->probablyCan( 'create', $this->title ) |
269 | ); |
270 | if ( $this->performer->isRegistered() ) { |
271 | $legacyUser = $this->userFactory->newFromAuthority( $this->performer ); |
272 | $blocked = $this->permissionManager->isBlockedFrom( |
273 | $legacyUser, $this->title, true |
274 | ); |
275 | } else { |
276 | $blocked = false; |
277 | } |
278 | return $this->isCurrentPageContentModelEditable() && $userQuickEditCheck && !$blocked; |
279 | } |
280 | |
281 | /** |
282 | * Checks whether the user has the permissions to move the current page. |
283 | * |
284 | * @return bool |
285 | */ |
286 | private function canMove() { |
287 | if ( !$this->title ) { |
288 | return false; |
289 | } |
290 | |
291 | return $this->performer->probablyCan( 'move', $this->title ) |
292 | && $this->title->exists(); |
293 | } |
294 | |
295 | /** |
296 | * Checks whether the user has the permissions to delete the current page. |
297 | * |
298 | * @return bool |
299 | */ |
300 | private function canDelete() { |
301 | if ( !$this->title ) { |
302 | return false; |
303 | } |
304 | |
305 | return $this->performer->probablyCan( 'delete', $this->title ) |
306 | && $this->title->exists(); |
307 | } |
308 | |
309 | /** |
310 | * Checks whether the user has the permissions to change the protections status of the current page. |
311 | * |
312 | * @return bool |
313 | */ |
314 | private function canProtect() { |
315 | if ( !$this->title ) { |
316 | return false; |
317 | } |
318 | |
319 | return $this->performer->probablyCan( 'protect', $this->title ) |
320 | && $this->title->exists(); |
321 | } |
322 | } |