Go to the documentation of this file.
55 $a = mt_rand( 0, 100 );
56 $b = mt_rand( 0, 10 );
60 $op = mt_rand( 0, 1 ) ?
'+' :
'−';
65 $answer = ( $op ==
'+' ) ? ( $a + $b ) : ( $a - $b );
66 return [
'question' => $test,
'answer' => $answer ];
76 $resultArr[
'captcha'][
'id'] = $index;
77 $resultArr[
'captcha'][
'question'] = $captcha[
'question'];
88 'mime' =>
'text/plain',
127 new OOUI\FieldLayout(
128 new OOUI\NumberInputWidget( [
129 'name' =>
'wpCaptchaWord',
130 'classes' => [
'simplecaptcha-answer' ],
131 'id' =>
'wpCaptchaWord',
132 'autocomplete' =>
'off',
134 'tabIndex' => $tabIndex
138 'label' => $captcha[
'question'] .
' = ',
139 'classes' => [
'simplecaptcha-field' ],
142 new OOUI\HiddenInputWidget( [
143 'name' =>
'wpCaptchaId',
144 'id' =>
'wpCaptchaId',
148 'ext.confirmEdit.simpleCaptcha'
172 if ( !$formInformation ) {
175 if ( isset( $formInformation[
'html'] ) ) {
176 $out->addHTML( $formInformation[
'html'] );
178 if ( isset( $formInformation[
'modules'] ) ) {
179 $out->addModules( $formInformation[
'modules'] );
181 if ( isset( $formInformation[
'modulestyles'] ) ) {
182 $out->addModuleStyles( $formInformation[
'modulestyles'] );
184 if ( isset( $formInformation[
'headitems'] ) ) {
185 $out->addHeadItems( $formInformation[
'headitems'] );
195 return $captchaData[
'question'] .
' =';
205 $page = $editPage->getArticle()->getPage();
206 if ( !isset( $page->ConfirmEdit_ActivateCaptcha ) ) {
210 if ( $this->action !==
'edit' ) {
211 unset( $page->ConfirmEdit_ActivateCaptcha );
212 $out->addHTML( $this->
getMessage( $this->action )->parseAsBlock() );
222 $context = $editPage->getArticle()->getContext();
223 $page = $editPage->getArticle()->getPage();
225 if ( isset( $page->ConfirmEdit_ActivateCaptcha ) ||
226 $this->shouldCheck( $page,
'',
'',
$context )
228 $out->addHTML( $this->
getMessage( $this->action )->parseAsBlock() );
231 unset( $page->ConfirmEdit_ActivateCaptcha );
244 $name = static::$messagePrefix .
$action;
248 return $msg->isDisabled() ?
wfMessage( static::$messagePrefix .
'edit' ) : $msg;
258 $out = $form->getOutput();
259 $user = $form->getUser();
261 $this->action =
'sendemail';
266 $formMetainfo = $formInformation;
267 unset( $formMetainfo[
'html'] );
269 $form->addFooterText(
270 "<div class='captcha'>" .
271 $this->
getMessage(
'sendemail' )->parseAsBlock() .
272 $formInformation[
'html'] .
285 global $wgCaptchaBadLoginExpiration, $wgCaptchaBadLoginPerUserExpiration;
291 $cache->incrWithInit( $key, $wgCaptchaBadLoginExpiration );
296 $cache->incrWithInit( $key, $wgCaptchaBadLoginPerUserExpiration );
318 global $wgCaptchaBadLoginAttempts;
322 && (int)
$cache->get( $this->badLoginKey(
$cache ) ) >= $wgCaptchaBadLoginAttempts;
332 global $wgCaptchaBadLoginPerUserAttempts;
336 if ( is_object( $u ) ) {
341 && (int)
$cache->get( $badLoginPerUserKey ) >= $wgCaptchaBadLoginPerUserAttempts;
356 if ( $wgCaptchaWhitelistIP ) {
362 $whitelistMsg =
wfMessage(
'captcha-ip-whitelist' )->inContentLanguage();
363 if ( !$whitelistMsg->isDisabled() ) {
381 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
382 $cacheKey =
$cache->makeKey(
'confirmedit',
'ipwhitelist' );
384 $cachedWhitelist =
$cache->get( $cacheKey );
385 if ( $cachedWhitelist ===
false ) {
389 explode(
"\n", $msg->plain() )
394 $cache->set( $cacheKey, $whitelist, 86400 );
397 $whitelist = $cachedWhitelist;
416 $ips = array_map(
'trim', $input );
417 $ips = array_filter( $ips );
420 foreach ( $ips as $ip ) {
438 return $cache->makeGlobalKey(
'captcha',
'badlogin',
'ip', $ip );
450 return $cache->makeGlobalKey(
451 'captcha',
'badlogin',
'user', md5( $username )
466 return $answer == $info[
'answer'];
492 global $wgCaptchaTriggers, $wgCaptchaTriggersOnNamespace;
495 $triggers = $wgCaptchaTriggers;
498 if ( is_array( $attributeCaptchaTriggers ) ) {
499 $triggers += $attributeCaptchaTriggers;
502 if ( isset( $triggers[
$action] ) ) {
508 isset( $wgCaptchaTriggersOnNamespace[
$title->getNamespace()][
$action] )
510 $result = $wgCaptchaTriggersOnNamespace[
$title->getNamespace()][
$action];
542 $newtext =
$content->getNativeData();
554 $this->trigger = sprintf(
"edit trigger by '%s' at [[%s]]",
556 $title->getPrefixedText() );
557 $this->action =
'edit';
558 wfDebug(
"ConfirmEdit: checking all edits...\n" );
564 $this->trigger = sprintf(
"Create trigger by '%s' at [[%s]]",
566 $title->getPrefixedText() );
567 $this->action =
'create';
568 wfDebug(
"ConfirmEdit: checking on page creation...\n" );
574 if ( !$request->wasPosted() ) {
576 "ConfirmEdit: request not posted, assuming that no content will be saved -> no CAPTCHA check"
588 if ( $editInfo->output ) {
589 $newLinks = array_keys( $editInfo->output->getExternalLinks() );
595 if ( $oldtext ===
null ) {
602 $unknownLinks = array_filter( $newLinks, [ $this,
'filterLink' ] );
603 $addedLinks = array_diff( $unknownLinks, $oldLinks );
604 $numLinks = count( $addedLinks );
606 if ( $numLinks > 0 ) {
607 $this->trigger = sprintf(
"%dx url trigger by '%s' at [[%s]]: %s",
610 $title->getPrefixedText(),
611 implode(
", ", $addedLinks ) );
612 $this->action =
'addurl';
617 global $wgCaptchaRegexes;
618 if ( $newtext !==
null && $wgCaptchaRegexes ) {
619 if ( !is_array( $wgCaptchaRegexes ) ) {
620 throw new UnexpectedValueException(
621 '$wgCaptchaRegexes is required to be an array, ' . gettype( $wgCaptchaRegexes ) .
' given.'
625 if ( $oldtext ===
null ) {
629 foreach ( $wgCaptchaRegexes as $regex ) {
631 if ( preg_match_all( $regex, $newtext, $newMatches ) ) {
633 preg_match_all( $regex, $oldtext, $oldMatches );
635 $addedMatches = array_diff( $newMatches[0], $oldMatches[0] );
637 $numHits = count( $addedMatches );
638 if ( $numHits > 0 ) {
639 $this->trigger = sprintf(
"%dx %s at [[%s]]: %s",
643 $title->getPrefixedText(),
644 implode(
", ", $addedMatches ) );
645 $this->action =
'edit';
661 global $wgCaptchaWhitelist;
662 static $regexes =
null;
664 if ( $regexes ===
null ) {
667 $regexes =
$source->isDisabled()
671 if ( $wgCaptchaWhitelist !==
false ) {
672 array_unshift( $regexes, $wgCaptchaWhitelist );
676 foreach ( $regexes as $regex ) {
677 if ( preg_match( $regex, $url ) ) {
692 # Code duplicated from the SpamBlacklist extension (r19197)
693 # and later modified.
695 # Strip comments and whitespace, then remove blanks
696 $lines = array_filter( array_map(
'trim', preg_replace(
'/#.*$/',
'',
$lines ) ) );
698 # No lines, don't make a regex which will match everything
699 if ( count(
$lines ) == 0 ) {
704 # It's faster using the S modifier even though it will usually only be run once
709 'normal' =>
'/^(?:https?:)?\/\/+[a-z0-9_\-.]*(?:',
710 'noprotocol' =>
'/^(?:',
714 'noprotocol' =>
')/Si',
719 # Extract flags from the line
721 if ( preg_match(
'/^(.*?)\s*<([^<>]*)>$/',
$line,
$matches ) ) {
723 wfDebug(
"Line with empty regex\n" );
727 $opts = preg_split(
'/\s*\|\s*/', trim(
$matches[2] ) );
728 foreach ( $opts as $opt ) {
729 $opt = strtolower( $opt );
730 if ( $opt ==
'noprotocol' ) {
731 $options[
'noprotocol'] =
true;
736 $key = isset( $options[
'noprotocol'] ) ?
'noprotocol' :
'normal';
739 if ( !isset( $build[$key] ) ) {
740 $build[$key] =
$line;
741 } elseif ( strlen( $build[$key] ) + strlen(
$line ) > $regexMax ) {
742 $regexes[] = $regexStart[$key] .
743 str_replace(
'/',
'\/', preg_replace(
'|\\\*/|',
'/', $build[$key] ) ) .
745 $build[$key] =
$line;
747 $build[$key] .=
'|' .
$line;
750 foreach ( $build as $key => $value ) {
751 $regexes[] = $regexStart[$key] .
752 str_replace(
'/',
'\/', preg_replace(
'|\\\*/|',
'/', $build[$key] ) ) .
767 $id =
$title->getArticleID();
768 $res =
$dbr->select(
'externallinks', [
'el_to' ],
769 [
'el_from' => $id ], __METHOD__ );
771 foreach (
$res as $row ) {
772 $links[] = $row->el_to;
791 if ( $request->getVal(
'captchaid' ) ) {
792 $request->setVal(
'wpCaptchaId', $request->getVal(
'captchaid' ) );
793 $wgRequest->setVal(
'wpCaptchaId', $request->getVal(
'captchaid' ) );
795 if ( $request->getVal(
'captchaword' ) ) {
796 $request->setVal(
'wpCaptchaWord', $request->getVal(
'captchaword' ) );
797 $wgRequest->setVal(
'wpCaptchaWord', $request->getVal(
'captchaword' ) );
802 wfDebug(
"ConfirmEdit: no need to show captcha.\n" );
818 if ( !
$context->canUseWikiPage() ) {
831 wfDebug( __METHOD__ .
': Skipped ConfirmEdit check: No WikiPage for title ' .
$title );
841 if ( $this->action ===
'edit' ) {
846 [
'class' =>
'errorbox' ],
847 $context->msg(
'captcha-edit-fail' )->text()
853 $page->ConfirmEdit_ActivateCaptcha =
true;
868 $creatingUser = $creatingUser ?: $wgUser;
872 \
MediaWiki\MediaWikiServices::getInstance()->getMainConfig() ) ) {
894 \
MediaWiki\MediaWikiServices::getInstance()->getMainConfig() ) ) {
898 if ( defined(
'MW_API' ) ) {
900 # Asking for captchas in the API is really silly
904 $this->trigger =
"{$wgUser->getName()} sending email";
929 $params[
'captchaword'] = [
932 $params[
'captchaid'] = [
958 $index = $request->
getVal(
'wpCaptchaId' );
959 $word = $request->
getVal(
'wpCaptchaWord' );
960 return [ $index, $word ];
977 $this->
log(
'User reached RateLimit, preventing action' );
1012 if ( isset( $this->captchaSolved ) ) {
1018 if ( $this->
keyMatch( $word, $info ) ) {
1019 $this->
log(
"passed" );
1021 $this->captchaSolved =
true;
1025 $this->
log(
"bad form input" );
1026 $this->captchaSolved =
false;
1030 $this->
log(
"new captcha session" );
1039 protected function log( $message ) {
1040 wfDebugLog(
'captcha',
'ConfirmEdit: ' . $message .
'; ' . $this->trigger );
1055 if ( !isset( $info[
'index'] ) ) {
1057 $info[
'index'] = strval( mt_rand() );
1060 return $info[
'index'];
1093 if ( is_null( $rev ) ) {
1099 if ( $section !==
'' ) {
1100 return $wgParser->getSection( $text, $section );
1116 $text =
$wgParser->preSaveTransform( $text,
$title, $wgUser, $options );
1119 return array_keys( $out->getExternalLinks() );
1128 $wgOut->addWikiMsg(
'captchahelp-text' );
1130 $wgOut->addWikiMsg(
'captchahelp-cookies-needed' );
1151 array $requests, array $fieldInfo, array &$formDescriptor,
$action
1153 $req = AuthenticationRequest::getRequestByClass( $requests,
1154 CaptchaAuthenticationRequest::class );
1159 $formDescriptor[
'captchaWord'] = [
1160 'label-message' =>
null,
1161 'autocomplete' =>
false,
1162 'persistent' =>
false,
1164 ] + $formDescriptor[
'captchaWord'];
1175 $allowConfirmEmail = $config->
get(
'AllowConfirmedEmail' );
1177 if ( $user->isAllowed(
'skipcaptcha' ) ) {
1178 wfDebug(
"ConfirmEdit: user group allows skipping captcha\n" );
1183 wfDebug(
"ConfirmEdit: user IP is whitelisted" );
1187 if ( $allowConfirmEmail && $user->isEmailConfirmed() ) {
1188 wfDebug(
"ConfirmEdit: user has confirmed mail, skipping captcha\n" );
A module that allows for editing and creating pages.
confirmEmailUser( $from, $to, $subject, $text, &$error)
Check the captcha on Special:EmailUser.
Set options of the Parser.
static get()
Get somewhere to store captcha data that will persist between requests.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
static getLocalClusterInstance()
Get the main cluster-local cache object.
loadText( $title, $section, $flags=Revision::READ_LATEST)
Retrieve the current version of the page or section being edited...
passCaptchaLimitedFromRequest(WebRequest $request, User $user)
Checks, if the user reached the amount of false CAPTCHAs and give him some vacation or run self::pass...
static isInRanges( $ip, $ranges)
Determines if an IP address is a list of CIDR a.b.c.d/n ranges.
addFormToOutput(OutputPage $out, $tabIndex=1)
Uses getFormInformation() to get the CAPTCHA form and adds it to the given OutputPage object.
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
keyMatch( $answer, $info)
Check if the submitted form matches the captcha session data provided by the plugin when the form was...
editShowCaptcha( $editPage)
Insert the captcha prompt into an edit form.
Class representing a MediaWiki article and history.
badLoginKey(BagOStuff $cache)
Internal cache key for badlogin checks.
captchaTriggers( $title, $action)
Class representing a cache/ephemeral data store.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
string $action
Used to select the right message.
const CONTENT_MODEL_WIKITEXT
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
showHelp()
Show a page explaining what this wacky thing is.
injectEmailUser(&$form)
Inject whazawhoo @fixme if multiple thingies insert a header, could break.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
buildRegexes( $lines)
Build regex from whitelist.
getCaptchaInfo( $captchaData, $id)
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target.
canSkipCaptcha( $user, Config $config)
Check whether the user provided / IP making the request is allowed to skip captchas.
Interface for configuration instances.
passCaptchaLimited( $index, $word, User $user)
Checks, if the user reached the amount of false CAPTCHAs and give him some vacation or run self::pass...
addCaptchaAPI(&$resultArr)
resetBadLoginCounter( $username)
Reset bad login counter after a successful login.
getCaptcha()
Returns an array with 'question' and 'answer' keys.
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
filterLink( $url)
Filter callback function for URL whitelisting.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
findLinks( $title, $text)
Extract a list of all recognized HTTP links in the text.
getWikiIPWhitelist(Message $msg)
Get the on-wiki IP whitelist stored in [[MediaWiki:Captcha-ip-whitelist]] page from cache if possible...
getTitle()
Get the title object of the article.
getCaptchaParamsFromRequest(WebRequest $request)
onAuthChangeFormFields(array $requests, array $fieldInfo, array &$formDescriptor, $action)
Modify the appearance of the captcha field.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
apiGetAllowedParams(&$module, &$params, $flags)
badLoginPerUserKey( $username, BagOStuff $cache)
Cache key for badloginPerUser checks.
doConfirmEdit(WikiPage $page, $newtext, $section, IContextSource $context)
Backend function for confirmEditMerged()
confirmEditMerged( $context, $content, $status, $summary, $user, $minorEdit)
An efficient edit filter callback based on the text after section merging.
isIPWhitelisted()
Check if the current IP is allowed to skip captchas.
isBadLoginTriggered()
Check if a bad login has already been registered for this IP address.
log( $message)
Log the status and any triggering info for debugging or statistics.
retrieveCaptcha( $index)
Fetch this session's captcha info.
createAuthenticationRequest()
needCreateAccountCaptcha(User $creatingUser=null)
Logic to check if we need to pass a captcha for the current user to create a new account,...
addFormInformationToOutput(OutputPage $out, array $formInformation)
Processes the given $formInformation array and adds the options (see getFormInformation()) to the giv...
Demo CAPTCHA (not for production usage) and base class for real CAPTCHAs.
getError()
Return the error from the last passCaptcha* call.
storeCaptcha( $info)
Generate a captcha session ID and save the info in PHP's session storage.
static getMain()
Get the RequestContext object associated with the main request.
const EXT_REG_ATTRIBUTE_NAME
getMessage( $action)
Show a message asking the user to enter a captcha on edit The result will be treated as wiki text.
Interface for objects which can provide a MediaWiki context on request.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
describeCaptchaType()
Describes the captcha type for API clients.
isBadLoginPerUserTriggered( $u)
Is the per-user captcha triggered?
Base interface for content objects.
isAPICaptchaModule( $module)
Represents a title within MediaWiki.
string $trigger
Used in log messages.
static getContentText(Content $content=null)
Convenience function for getting flat text from a Content object.
shouldCheck(WikiPage $page, $content, $section, $context, $oldtext=null)
increaseBadLoginCounter( $username)
Increase bad login counter after a failed login.
boolean null $captchaSolved
Was the CAPTCHA already passed and if yes, with which result?
getVal( $name, $default=null)
Fetch a scalar from the input or return $default if it's not set.
passCaptcha( $index, $word)
Given a required captcha run, test form input for correct input on the open session.
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid.
getLinksFromTracker( $title)
Load external links from the externallinks table.
prepareContentForEdit(Content $content, $revision=null, User $user=null, $serialFormat=null, $useCache=true)
Prepare content which is about to be saved.
if(! $wgDBerrorLogTZ) $wgRequest
triggersCaptcha( $action, $title=null)
Checks, whether the passed action should trigger a CAPTCHA.
Variant of the Message class.
showEditFormFields(&$editPage, &$out)
Show error message for missing or incorrect captcha on EditPage.
getFormInformation( $tabIndex=1)
Insert a captcha prompt into the edit form.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
passCaptchaFromRequest(WebRequest $request, User $user)
Given a required captcha run, test form input for correct input on the open session.
const AS_HOOK_ERROR_EXPECTED
Status: A hook function returned an error.
static isIPAddress( $ip)
Determine if a string is as valid IP address or network (CIDR prefix).
Generic captcha authentication request class.
buildValidIPs(array $input)
From a list of unvalidated input, get all the valid IP addresses and IP ranges from it.
clearCaptcha( $index)
Clear out existing captcha info from the session, to ensure it can't be reused.