Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 97 |
|
0.00% |
0 / 14 |
CRAP | |
0.00% |
0 / 1 |
Hooks | |
0.00% |
0 / 96 |
|
0.00% |
0 / 14 |
930 | |
0.00% |
0 / 1 |
getInstance | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
onEditFilterMergedContent | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
onPageSaveComplete | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
onEditPageBeforeEditButtons | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onEditPage__showEditForm_fields | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onEmailUserForm | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onEmailUser | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onAPIGetAllowedParams | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onAuthChangeFormFields | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
confirmEditSetup | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
12 | |||
onTitleReadWhitelist | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
onFancyCaptchaSetup | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
onAlternateEditPreview | |
0.00% |
0 / 49 |
|
0.00% |
0 / 1 |
42 | |||
onResourceLoaderRegisterModules | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | // phpcs:disable MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName |
4 | |
5 | namespace MediaWiki\Extension\ConfirmEdit; |
6 | |
7 | use ApiBase; |
8 | use Content; |
9 | use ExtensionRegistry; |
10 | use HTMLForm; |
11 | use IContextSource; |
12 | use MailAddress; |
13 | use MediaWiki\Api\Hook\APIGetAllowedParamsHook; |
14 | use MediaWiki\EditPage\EditPage; |
15 | use MediaWiki\Extension\ConfirmEdit\SimpleCaptcha\SimpleCaptcha; |
16 | use MediaWiki\Hook\AlternateEditPreviewHook; |
17 | use MediaWiki\Hook\EditFilterMergedContentHook; |
18 | use MediaWiki\Hook\EditPage__showEditForm_fieldsHook; |
19 | use MediaWiki\Hook\EditPageBeforeEditButtonsHook; |
20 | use MediaWiki\Hook\EmailUserFormHook; |
21 | use MediaWiki\Hook\EmailUserHook; |
22 | use MediaWiki\Html\Html; |
23 | use MediaWiki\MediaWikiServices; |
24 | use MediaWiki\Output\OutputPage; |
25 | use MediaWiki\Permissions\Hook\TitleReadWhitelistHook; |
26 | use MediaWiki\ResourceLoader\Hook\ResourceLoaderRegisterModulesHook; |
27 | use MediaWiki\ResourceLoader\ResourceLoader; |
28 | use MediaWiki\Revision\RevisionRecord; |
29 | use MediaWiki\SpecialPage\Hook\AuthChangeFormFieldsHook; |
30 | use MediaWiki\SpecialPage\SpecialPage; |
31 | use MediaWiki\Status\Status; |
32 | use MediaWiki\Storage\EditResult; |
33 | use MediaWiki\Storage\Hook\PageSaveCompleteHook; |
34 | use MediaWiki\Title\Title; |
35 | use MediaWiki\User\User; |
36 | use MediaWiki\User\UserIdentity; |
37 | use MessageSpecifier; |
38 | use ParserOutput; |
39 | use Wikimedia\IPUtils; |
40 | use WikiPage; |
41 | |
42 | class Hooks implements |
43 | AlternateEditPreviewHook, |
44 | EditPageBeforeEditButtonsHook, |
45 | EmailUserFormHook, |
46 | EmailUserHook, |
47 | TitleReadWhitelistHook, |
48 | ResourceLoaderRegisterModulesHook, |
49 | PageSaveCompleteHook, |
50 | EditPage__showEditForm_fieldsHook, |
51 | EditFilterMergedContentHook, |
52 | APIGetAllowedParamsHook, |
53 | AuthChangeFormFieldsHook |
54 | { |
55 | |
56 | protected static $instanceCreated = false; |
57 | |
58 | /** |
59 | * Get the global Captcha instance |
60 | * |
61 | * @return SimpleCaptcha |
62 | */ |
63 | public static function getInstance() { |
64 | global $wgCaptcha, $wgCaptchaClass; |
65 | |
66 | if ( !static::$instanceCreated ) { |
67 | static::$instanceCreated = true; |
68 | $class = $wgCaptchaClass ?: SimpleCaptcha::class; |
69 | $wgCaptcha = new $class; |
70 | } |
71 | |
72 | return $wgCaptcha; |
73 | } |
74 | |
75 | /** |
76 | * @param IContextSource $context |
77 | * @param Content $content |
78 | * @param Status $status |
79 | * @param string $summary |
80 | * @param User $user |
81 | * @param bool $minorEdit |
82 | * @return bool |
83 | */ |
84 | public function onEditFilterMergedContent( IContextSource $context, Content $content, Status $status, |
85 | $summary, User $user, $minorEdit |
86 | ) { |
87 | return self::getInstance()->confirmEditMerged( $context, $content, $status, $summary, |
88 | $user, $minorEdit ); |
89 | } |
90 | |
91 | /** |
92 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/PageSaveComplete |
93 | * |
94 | * @param WikiPage $wikiPage |
95 | * @param UserIdentity $user |
96 | * @param string $summary |
97 | * @param int $flags |
98 | * @param RevisionRecord $revisionRecord |
99 | * @param EditResult $editResult |
100 | * @return bool|void |
101 | */ |
102 | public function onPageSaveComplete( |
103 | $wikiPage, |
104 | $user, |
105 | $summary, |
106 | $flags, |
107 | $revisionRecord, |
108 | $editResult |
109 | ) { |
110 | $title = $wikiPage->getTitle(); |
111 | if ( $title->getText() === 'Captcha-ip-whitelist' && $title->getNamespace() === NS_MEDIAWIKI ) { |
112 | $cache = MediaWikiServices::getInstance()->getMainWANObjectCache(); |
113 | $cache->delete( $cache->makeKey( 'confirmedit', 'ipwhitelist' ) ); |
114 | } |
115 | |
116 | return true; |
117 | } |
118 | |
119 | /** |
120 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/EditPageBeforeEditButtons |
121 | * |
122 | * @param EditPage $editpage Current EditPage object |
123 | * @param array &$buttons Array of edit buttons, "Save", "Preview", "Live", and "Diff" |
124 | * @param int &$tabindex HTML tabindex of the last edit check/button |
125 | */ |
126 | public function onEditPageBeforeEditButtons( $editpage, &$buttons, &$tabindex ) { |
127 | self::getInstance()->editShowCaptcha( $editpage ); |
128 | } |
129 | |
130 | /** |
131 | * @param EditPage $editPage |
132 | * @param OutputPage $out |
133 | */ |
134 | public function onEditPage__showEditForm_fields( $editPage, $out ) { |
135 | self::getInstance()->showEditFormFields( $editPage, $out ); |
136 | } |
137 | |
138 | /** |
139 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/EmailUserForm |
140 | * |
141 | * @param HTMLForm &$form HTMLForm object |
142 | * @return bool|void True or no return value to continue or false to abort |
143 | */ |
144 | public function onEmailUserForm( &$form ) { |
145 | return self::getInstance()->injectEmailUser( $form ); |
146 | } |
147 | |
148 | /** |
149 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/EmailUser |
150 | * |
151 | * @param MailAddress &$to MailAddress object of receiving user |
152 | * @param MailAddress &$from MailAddress object of sending user |
153 | * @param string &$subject subject of the mail |
154 | * @param string &$text text of the mail |
155 | * @param bool|Status|MessageSpecifier|array &$error Out-param for an error. |
156 | * Should be set to a Status object or boolean false. |
157 | * @return bool|void True or no return value to continue or false to abort |
158 | */ |
159 | public function onEmailUser( &$to, &$from, &$subject, &$text, &$error ) { |
160 | return self::getInstance()->confirmEmailUser( $from, $to, $subject, $text, $error ); |
161 | } |
162 | |
163 | /** |
164 | * APIGetAllowedParams hook handler |
165 | * Default $flags to 1 for backwards-compatible behavior |
166 | * @param ApiBase $module |
167 | * @param array &$params |
168 | * @param int $flags |
169 | * @return bool |
170 | */ |
171 | public function onAPIGetAllowedParams( $module, &$params, $flags ) { |
172 | return self::getInstance()->apiGetAllowedParams( $module, $params, $flags ); |
173 | } |
174 | |
175 | /** |
176 | * @param array $requests |
177 | * @param array $fieldInfo |
178 | * @param array &$formDescriptor |
179 | * @param string $action |
180 | */ |
181 | public function onAuthChangeFormFields( |
182 | $requests, $fieldInfo, &$formDescriptor, $action |
183 | ) { |
184 | self::getInstance()->onAuthChangeFormFields( $requests, $fieldInfo, $formDescriptor, $action ); |
185 | } |
186 | |
187 | public static function confirmEditSetup() { |
188 | global $wgCaptchaTriggers; |
189 | |
190 | // There is no need to run (core) tests with enabled ConfirmEdit - bug T44145 |
191 | if ( defined( 'MW_PHPUNIT_TEST' ) || defined( 'MW_QUIBBLE_CI' ) ) { |
192 | $wgCaptchaTriggers = array_fill_keys( array_keys( $wgCaptchaTriggers ), false ); |
193 | } |
194 | } |
195 | |
196 | /** |
197 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/TitleReadWhitelist |
198 | * |
199 | * @param Title $title |
200 | * @param User $user |
201 | * @param bool &$whitelisted |
202 | * @return bool|void |
203 | */ |
204 | public function onTitleReadWhitelist( $title, $user, &$whitelisted ) { |
205 | $image = SpecialPage::getTitleFor( 'Captcha', 'image' ); |
206 | $help = SpecialPage::getTitleFor( 'Captcha', 'help' ); |
207 | if ( $title->equals( $image ) || $title->equals( $help ) ) { |
208 | $whitelisted = true; |
209 | } |
210 | } |
211 | |
212 | /** |
213 | * |
214 | * Callback for extension.json of FancyCaptcha to set a default captcha directory, |
215 | * which depends on wgUploadDirectory |
216 | */ |
217 | public static function onFancyCaptchaSetup() { |
218 | global $wgCaptchaDirectory, $wgUploadDirectory; |
219 | if ( !$wgCaptchaDirectory ) { |
220 | $wgCaptchaDirectory = "$wgUploadDirectory/captcha"; |
221 | } |
222 | } |
223 | |
224 | /** |
225 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/AlternateEditPreview |
226 | * |
227 | * @param EditPage $editPage |
228 | * @param Content &$content |
229 | * @param string &$previewHTML |
230 | * @param ParserOutput &$parserOutput |
231 | * @return bool|void |
232 | */ |
233 | public function onAlternateEditPreview( $editPage, &$content, &$previewHTML, |
234 | &$parserOutput |
235 | ) { |
236 | $title = $editPage->getTitle(); |
237 | $exceptionTitle = Title::makeTitle( NS_MEDIAWIKI, 'Captcha-ip-whitelist' ); |
238 | |
239 | if ( !$title->equals( $exceptionTitle ) ) { |
240 | return true; |
241 | } |
242 | |
243 | $ctx = $editPage->getArticle()->getContext(); |
244 | $out = $ctx->getOutput(); |
245 | $lang = $ctx->getLanguage(); |
246 | |
247 | $lines = explode( "\n", $content->getNativeData() ); |
248 | $previewHTML .= Html::warningBox( |
249 | $ctx->msg( 'confirmedit-preview-description' )->parse() |
250 | ) . |
251 | Html::openElement( |
252 | 'table', |
253 | [ 'class' => 'wikitable sortable' ] |
254 | ) . |
255 | Html::openElement( 'thead' ) . |
256 | Html::element( 'th', [], $ctx->msg( 'confirmedit-preview-line' )->text() ) . |
257 | Html::element( 'th', [], $ctx->msg( 'confirmedit-preview-content' )->text() ) . |
258 | Html::element( 'th', [], $ctx->msg( 'confirmedit-preview-validity' )->text() ) . |
259 | Html::closeElement( 'thead' ); |
260 | |
261 | foreach ( $lines as $count => $line ) { |
262 | $ip = trim( $line ); |
263 | if ( $ip === '' || strpos( $ip, '#' ) !== false ) { |
264 | continue; |
265 | } |
266 | if ( IPUtils::isIPAddress( $ip ) ) { |
267 | $validity = $ctx->msg( 'confirmedit-preview-valid' )->escaped(); |
268 | $css = 'valid'; |
269 | } else { |
270 | $validity = $ctx->msg( 'confirmedit-preview-invalid' )->escaped(); |
271 | $css = 'notvalid'; |
272 | } |
273 | $previewHTML .= Html::openElement( 'tr' ) . |
274 | Html::element( |
275 | 'td', |
276 | [], |
277 | $lang->formatNum( $count + 1 ) |
278 | ) . |
279 | Html::element( |
280 | 'td', |
281 | [], |
282 | // IPv6 max length: 8 groups * 4 digits + 7 delimiter = 39 |
283 | // + 11 chars for safety |
284 | $lang->truncateForVisual( $ip, 50 ) |
285 | ) . |
286 | Html::rawElement( |
287 | 'td', |
288 | // possible values: |
289 | // mw-confirmedit-ip-valid |
290 | // mw-confirmedit-ip-notvalid |
291 | [ 'class' => 'mw-confirmedit-ip-' . $css ], |
292 | $validity |
293 | ) . |
294 | Html::closeElement( 'tr' ); |
295 | } |
296 | $previewHTML .= Html::closeElement( 'table' ); |
297 | $out->addModuleStyles( 'ext.confirmEdit.editPreview.ipwhitelist.styles' ); |
298 | |
299 | return false; |
300 | } |
301 | |
302 | /** |
303 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/ResourceLoaderRegisterModules |
304 | * |
305 | * @param ResourceLoader $resourceLoader |
306 | * @return void |
307 | */ |
308 | public function onResourceLoaderRegisterModules( ResourceLoader $resourceLoader ): void { |
309 | $extensionRegistry = ExtensionRegistry::getInstance(); |
310 | $messages = []; |
311 | |
312 | $messages[] = 'colon-separator'; |
313 | $messages[] = 'captcha-edit'; |
314 | $messages[] = 'captcha-label'; |
315 | |
316 | if ( $extensionRegistry->isLoaded( 'QuestyCaptcha' ) ) { |
317 | $messages[] = 'questycaptcha-edit'; |
318 | } |
319 | |
320 | if ( $extensionRegistry->isLoaded( 'FancyCaptcha' ) ) { |
321 | $messages[] = 'fancycaptcha-edit'; |
322 | $messages[] = 'fancycaptcha-reload-text'; |
323 | $messages[] = 'fancycaptcha-imgcaptcha-ph'; |
324 | } |
325 | |
326 | $resourceLoader->register( [ |
327 | 'ext.confirmEdit.CaptchaInputWidget' => [ |
328 | 'localBasePath' => dirname( __DIR__ ), |
329 | 'remoteExtPath' => 'ConfirmEdit', |
330 | 'scripts' => 'resources/libs/ext.confirmEdit.CaptchaInputWidget.js', |
331 | 'styles' => 'resources/libs/ext.confirmEdit.CaptchaInputWidget.less', |
332 | 'messages' => $messages, |
333 | 'dependencies' => 'oojs-ui-core', |
334 | ] |
335 | ] ); |
336 | } |
337 | |
338 | } |
339 | |
340 | class_alias( Hooks::class, 'ConfirmEditHooks' ); |