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 MediaWiki\Config\Config; |
23 | use MediaWiki\Config\ConfigException; |
24 | use MediaWiki\Content\ContentHandler; |
25 | use MediaWiki\Content\IContentHandlerFactory; |
26 | use MediaWiki\Context\IContextSource; |
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 | /** @var Title Current page title */ |
42 | private ?Title $title; |
43 | /** @var Config Extension config */ |
44 | private Config $config; |
45 | private Authority $performer; |
46 | private OutputPage $out; |
47 | private ContentHandler $contentHandler; |
48 | private SkinOptions $skinOptions; |
49 | private LanguagesHelper $languagesHelper; |
50 | private PermissionManager $permissionManager; |
51 | private IContentHandlerFactory $contentHandlerFactory; |
52 | private UserFactory $userFactory; |
53 | private WatchlistManager $watchlistManager; |
54 | |
55 | /** |
56 | * Initialize internal Minerva Permissions system |
57 | * @param SkinOptions $skinOptions |
58 | * @param LanguagesHelper $languagesHelper |
59 | * @param PermissionManager $permissionManager |
60 | * @param IContentHandlerFactory $contentHandlerFactory |
61 | * @param UserFactory $userFactory |
62 | * @param WatchlistManager $watchlistManager |
63 | */ |
64 | public function __construct( |
65 | SkinOptions $skinOptions, |
66 | LanguagesHelper $languagesHelper, |
67 | PermissionManager $permissionManager, |
68 | IContentHandlerFactory $contentHandlerFactory, |
69 | UserFactory $userFactory, |
70 | WatchlistManager $watchlistManager |
71 | ) { |
72 | $this->skinOptions = $skinOptions; |
73 | $this->languagesHelper = $languagesHelper; |
74 | $this->permissionManager = $permissionManager; |
75 | $this->contentHandlerFactory = $contentHandlerFactory; |
76 | $this->userFactory = $userFactory; |
77 | $this->watchlistManager = $watchlistManager; |
78 | } |
79 | |
80 | /** |
81 | * @param IContextSource $context |
82 | * @return $this |
83 | */ |
84 | public function setContext( IContextSource $context ): self { |
85 | $this->title = $context->getTitle(); |
86 | $this->config = $context->getConfig(); |
87 | $this->performer = $context->getAuthority(); |
88 | $this->out = $context->getOutput(); |
89 | // Title may be undefined in certain contexts (T179833) |
90 | // TODO: Check if this is still true if we always pass a context instead of using global one |
91 | if ( $this->title ) { |
92 | $this->contentHandler = $this->contentHandlerFactory->getContentHandler( |
93 | $this->title->getContentModel() |
94 | ); |
95 | } |
96 | return $this; |
97 | } |
98 | |
99 | /** |
100 | * Gets whether or not the action is allowed. |
101 | * |
102 | * Actions isn't allowed when: |
103 | * <ul> |
104 | * <li>the user is on the main page</li> |
105 | * </ul> |
106 | * |
107 | * The "edit" action is not allowed if editing is not possible on the page |
108 | * @see method isCurrentPageContentModelEditable |
109 | * |
110 | * The "switch-language" is allowed if there are interlanguage links on the page, |
111 | * or <code>$wgMinervaAlwaysShowLanguageButton</code> is truthy. |
112 | * |
113 | * @inheritDoc |
114 | * @throws ConfigException |
115 | */ |
116 | public function isAllowed( $action ): bool { |
117 | if ( !$this->title ) { |
118 | return false; |
119 | } |
120 | |
121 | // T206406: Enable "Talk" or "Discussion" button on Main page, also, not forgetting |
122 | // the "switch-language" button. But disable "edit" and "watch" actions. |
123 | if ( $this->title->isMainPage() ) { |
124 | if ( $action === self::SWITCH_LANGUAGE ) { |
125 | return !$this->config->get( MainConfigNames::HideInterlanguageLinks ); |
126 | } |
127 | // Only the talk page is allowed on the main page provided user is registered. |
128 | // talk page permission is disabled on mobile for anons |
129 | // https://phabricator.wikimedia.org/T54165 |
130 | return $action === self::TALK && $this->performer->isRegistered(); |
131 | } |
132 | |
133 | if ( $action === self::TALK ) { |
134 | return ( |
135 | $this->title->isTalkPage() || |
136 | $this->title->canHaveTalkPage() |
137 | ); |
138 | } |
139 | |
140 | if ( $action === self::HISTORY && $this->title->exists() ) { |
141 | return $this->skinOptions->get( SkinOptions::HISTORY_IN_PAGE_ACTIONS ); |
142 | } |
143 | |
144 | if ( $action === SkinOptions::TOOLBAR_SUBMENU ) { |
145 | return $this->skinOptions->get( SkinOptions::TOOLBAR_SUBMENU ); |
146 | } |
147 | |
148 | if ( $action === self::EDIT_OR_CREATE ) { |
149 | return $this->canEditOrCreate(); |
150 | } |
151 | |
152 | if ( $action === self::CONTENT_EDIT ) { |
153 | return $this->isCurrentPageContentModelEditable(); |
154 | } |
155 | |
156 | if ( $action === self::WATCHABLE || $action === self::WATCH ) { |
157 | $isWatchable = $this->watchlistManager->isWatchable( $this->title ); |
158 | |
159 | if ( $action === self::WATCHABLE ) { |
160 | return $isWatchable; |
161 | } |
162 | if ( $action === self::WATCH ) { |
163 | return $isWatchable && |
164 | $this->performer->isAllowedAll( 'viewmywatchlist', 'editmywatchlist' ); |
165 | } |
166 | } |
167 | |
168 | if ( $action === self::SWITCH_LANGUAGE ) { |
169 | if ( $this->config->get( MainConfigNames::HideInterlanguageLinks ) ) { |
170 | return false; |
171 | } |
172 | return $this->languagesHelper->doesTitleHasLanguagesOrVariants( $this->out, $this->title ) || |
173 | $this->config->get( 'MinervaAlwaysShowLanguageButton' ); |
174 | } |
175 | |
176 | if ( $action === self::MOVE ) { |
177 | return $this->canMove(); |
178 | } |
179 | |
180 | if ( $action === self::DELETE ) { |
181 | return $this->canDelete(); |
182 | } |
183 | |
184 | if ( $action === self::PROTECT ) { |
185 | return $this->canProtect(); |
186 | } |
187 | |
188 | // Unknown action has been passed. |
189 | return false; |
190 | } |
191 | |
192 | /** |
193 | * @inheritDoc |
194 | */ |
195 | public function isTalkAllowed(): bool { |
196 | return $this->isAllowed( self::TALK ); |
197 | } |
198 | |
199 | /** |
200 | * Checks whether the editor can handle the existing content handler type. |
201 | * |
202 | * @return bool |
203 | */ |
204 | protected function isCurrentPageContentModelEditable(): bool { |
205 | if ( !$this->contentHandler ) { |
206 | return false; |
207 | } |
208 | |
209 | if ( |
210 | $this->contentHandler->supportsDirectEditing() && |
211 | $this->contentHandler->supportsDirectApiEditing() |
212 | ) { |
213 | return true; |
214 | } |
215 | |
216 | // For content types with custom action=edit handlers, let them do their thing |
217 | if ( array_key_exists( 'edit', $this->contentHandler->getActionOverrides() ?? [] ) ) { |
218 | return true; |
219 | } |
220 | |
221 | return false; |
222 | } |
223 | |
224 | /** |
225 | * Returns true if $title page exists and is editable or is creatable by $user as determined by |
226 | * quick checks. |
227 | * @return bool |
228 | */ |
229 | private function canEditOrCreate(): bool { |
230 | if ( !$this->title ) { |
231 | return false; |
232 | } |
233 | |
234 | $userQuickEditCheck = |
235 | $this->performer->probablyCan( 'edit', $this->title ) && ( |
236 | $this->title->exists() || |
237 | $this->performer->probablyCan( 'create', $this->title ) |
238 | ); |
239 | if ( $this->performer->isRegistered() ) { |
240 | $legacyUser = $this->userFactory->newFromAuthority( $this->performer ); |
241 | $blocked = $this->permissionManager->isBlockedFrom( |
242 | $legacyUser, $this->title, true |
243 | ); |
244 | } else { |
245 | $blocked = false; |
246 | } |
247 | return $this->isCurrentPageContentModelEditable() && $userQuickEditCheck && !$blocked; |
248 | } |
249 | |
250 | /** |
251 | * Checks whether the user has the permissions to move the current page. |
252 | * |
253 | * @return bool |
254 | */ |
255 | private function canMove(): bool { |
256 | if ( !$this->title ) { |
257 | return false; |
258 | } |
259 | |
260 | return $this->performer->probablyCan( 'move', $this->title ) |
261 | && $this->title->exists(); |
262 | } |
263 | |
264 | /** |
265 | * Checks whether the user has the permissions to delete the current page. |
266 | * |
267 | * @return bool |
268 | */ |
269 | private function canDelete(): bool { |
270 | if ( !$this->title ) { |
271 | return false; |
272 | } |
273 | |
274 | return $this->performer->probablyCan( 'delete', $this->title ) |
275 | && $this->title->exists(); |
276 | } |
277 | |
278 | /** |
279 | * Checks whether the user has the permissions to change the protections status of the current page. |
280 | * |
281 | * @return bool |
282 | */ |
283 | private function canProtect(): bool { |
284 | if ( !$this->title ) { |
285 | return false; |
286 | } |
287 | |
288 | return $this->performer->probablyCan( 'protect', $this->title ) |
289 | && $this->title->exists(); |
290 | } |
291 | } |