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'] ) ) {
184 if ( isset( $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;
287 $cache = ObjectCache::getLocalClusterInstance();
291 $cache->incrWithInit( $key, $wgCaptchaBadLoginExpiration );
296 $cache->incrWithInit( $key, $wgCaptchaBadLoginPerUserExpiration );
306 $cache = ObjectCache::getLocalClusterInstance();
318 global $wgCaptchaBadLoginAttempts;
320 $cache = ObjectCache::getLocalClusterInstance();
322 && (int)
$cache->get( $this->badLoginKey(
$cache ) ) >= $wgCaptchaBadLoginAttempts;
332 global $wgCaptchaBadLoginPerUserAttempts;
334 $cache = ObjectCache::getLocalClusterInstance();
336 if ( is_object( $u ) ) {
341 && (int)
$cache->get( $badLoginPerUserKey ) >= $wgCaptchaBadLoginPerUserAttempts;
356 if ( $wgCaptchaWhitelistIP ) {
357 if ( IP::isInRanges( $ip, $wgCaptchaWhitelistIP ) ) {
362 $whitelistMsg =
wfMessage(
'captcha-ip-whitelist' )->inContentLanguage();
363 if ( !$whitelistMsg->isDisabled() ) {
365 if ( IP::isInRanges( $ip, $whitelistedIPs ) ) {
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 ) {
421 if ( IP::isIPAddress( $ip ) ) {
438 return $cache->makeGlobalKey(
'captcha',
'badlogin',
'ip', $ip );
448 $username = User::getCanonicalName( $username,
'usable' ) ?: $username;
450 return $cache->makeGlobalKey(
451 'captcha',
'badlogin',
'user', md5( $username )
466 return $answer == $info[
'answer'];
492 global $wgCaptchaTriggers, $wgCaptchaTriggersOnNamespace;
495 $triggers = $wgCaptchaTriggers;
496 $attributeCaptchaTriggers = ExtensionRegistry::getInstance()
498 if ( is_array( $attributeCaptchaTriggers ) ) {
499 $triggers += $attributeCaptchaTriggers;
502 if ( isset( $triggers[
$action] ) ) {
503 $result = $triggers[$action];
508 isset( $wgCaptchaTriggersOnNamespace[
$title->getNamespace()][$action] )
510 $result = $wgCaptchaTriggersOnNamespace[
$title->getNamespace()][$action];
527 $context = RequestContext::getMain();
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 );
837 $status->apiHookResult = [];
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
901 $error = Status::newFatal(
'captcha-disabledinapi' );
904 $this->trigger =
"{$wgUser->getName()} sending email";
906 $error = Status::newFatal(
'captcha-sendemail-fail' );
929 $params[
'captchaword'] = [
930 ApiBase::PARAM_HELP_MSG =>
'captcha-apihelp-param-captchaword',
932 $params[
'captchaid'] = [
933 ApiBase::PARAM_HELP_MSG =>
'captcha-apihelp-param-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 ) ) {
1013 return $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'];
1092 $rev = Revision::newFromTitle(
$title,
false, $flags );
1093 if ( is_null( $rev ) ) {
1098 $text = ContentHandler::getContentText(
$content );
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" );
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
if(! $wgDBerrorLogTZ) $wgRequest
A module that allows for editing and creating pages.
Class representing a cache/ephemeral data store.
Generic captcha authentication request class.
static get()
Get somewhere to store captcha data that will persist between requests.
const EXT_REG_ATTRIBUTE_NAME
const AS_HOOK_ERROR_EXPECTED
Status: A hook function returned an error.
The Message class provides methods which fulfil two basic services:
plain()
Returns the message text as-is, only parameters are substituted.
This is one of the Core classes and should be read at least once by any new developers.
addModuleStyles( $modules)
Load the styles of one or more ResourceLoader modules on this page.
addHTML( $text)
Append $text to the body HTML.
addModules( $modules)
Load one or more ResourceLoader modules on this page.
addHeadItems( $values)
Add one or more head items to the output.
Set options of the Parser.
Variant of the Message class.
Demo CAPTCHA (not for production usage) and base class for real CAPTCHAs.
getMessage( $action)
Show a message asking the user to enter a captcha on edit The result will be treated as wiki text.
buildRegexes( $lines)
Build regex from whitelist.
findLinks( $title, $text)
Extract a list of all recognized HTTP links in the text.
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.
addFormToOutput(OutputPage $out, $tabIndex=1)
Uses getFormInformation() to get the CAPTCHA form and adds it to the given OutputPage object.
retrieveCaptcha( $index)
Fetch this session's captcha info.
log( $message)
Log the status and any triggering info for debugging or statistics.
isAPICaptchaModule( $module)
loadText( $title, $section, $flags=Revision::READ_LATEST)
Retrieve the current version of the page or section being edited...
triggersCaptcha( $action, $title=null)
Checks, whether the passed action should trigger a CAPTCHA.
keyMatch( $answer, $info)
Check if the submitted form matches the captcha session data provided by the plugin when the form was...
passCaptchaFromRequest(WebRequest $request, User $user)
Given a required captcha run, test form input for correct input on the open session.
getCaptchaInfo( $captchaData, $id)
showEditFormFields(&$editPage, &$out)
Show error message for missing or incorrect captcha on EditPage.
getError()
Return the error from the last passCaptcha* call.
editShowCaptcha( $editPage)
Insert the captcha prompt into an edit form.
needCreateAccountCaptcha(User $creatingUser=null)
Logic to check if we need to pass a captcha for the current user to create a new account,...
showHelp()
Show a page explaining what this wacky thing is.
filterLink( $url)
Filter callback function for URL whitelisting.
injectEmailUser(&$form)
Inject whazawhoo @fixme if multiple thingies insert a header, could break.
getLinksFromTracker( $title)
Load external links from the externallinks table.
string $trigger
Used in log messages.
getFormInformation( $tabIndex=1)
Insert a captcha prompt into the edit form.
string $action
Used to select the right message.
confirmEditMerged( $context, $content, $status, $summary, $user, $minorEdit)
An efficient edit filter callback based on the text after section merging.
buildValidIPs(array $input)
From a list of unvalidated input, get all the valid IP addresses and IP ranges from it.
apiGetAllowedParams(&$module, &$params, $flags)
isBadLoginPerUserTriggered( $u)
Is the per-user captcha triggered?
confirmEmailUser( $from, $to, $subject, $text, &$error)
Check the captcha on Special:EmailUser.
passCaptchaLimited( $index, $word, User $user)
Checks, if the user reached the amount of false CAPTCHAs and give him some vacation or run self::pass...
passCaptchaLimitedFromRequest(WebRequest $request, User $user)
Checks, if the user reached the amount of false CAPTCHAs and give him some vacation or run self::pass...
addFormInformationToOutput(OutputPage $out, array $formInformation)
Processes the given $formInformation array and adds the options (see getFormInformation()) to the giv...
getWikiIPWhitelist(Message $msg)
Get the on-wiki IP whitelist stored in [[MediaWiki:Captcha-ip-whitelist]] page from cache if possible...
badLoginPerUserKey( $username, BagOStuff $cache)
Cache key for badloginPerUser checks.
storeCaptcha( $info)
Generate a captcha session ID and save the info in PHP's session storage.
canSkipCaptcha( $user, Config $config)
Check whether the user provided / IP making the request is allowed to skip captchas.
badLoginKey(BagOStuff $cache)
Internal cache key for badlogin checks.
increaseBadLoginCounter( $username)
Increase bad login counter after a failed login.
captchaTriggers( $title, $action)
clearCaptcha( $index)
Clear out existing captcha info from the session, to ensure it can't be reused.
passCaptcha( $index, $word)
Given a required captcha run, test form input for correct input on the open session.
getCaptchaParamsFromRequest(WebRequest $request)
shouldCheck(WikiPage $page, $content, $section, $context, $oldtext=null)
addCaptchaAPI(&$resultArr)
doConfirmEdit(WikiPage $page, $newtext, $section, IContextSource $context)
Backend function for confirmEditMerged()
createAuthenticationRequest()
getCaptcha()
Returns an array with 'question' and 'answer' keys.
describeCaptchaType()
Describes the captcha type for API clients.
boolean null $captchaSolved
Was the CAPTCHA already passed and if yes, with which result?
onAuthChangeFormFields(array $requests, array $fieldInfo, array &$formDescriptor, $action)
Modify the appearance of the captcha field.
resetBadLoginCounter( $username)
Reset bad login counter after a successful login.
Represents a title within MediaWiki.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
getVal( $name, $default=null)
Fetch a scalar from the input or return $default if it's not set.
Class representing a MediaWiki article and history.
prepareContentForEdit(Content $content, $revision=null, User $user=null, $serialFormat=null, $useCache=true)
Prepare content which is about to be saved.
getTitle()
Get the title object of the article.
const CONTENT_MODEL_WIKITEXT
Interface for configuration instances.
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
Base interface for content objects.
Interface for objects which can provide a MediaWiki context on request.