MediaWiki  1.29.2
Captcha.php
Go to the documentation of this file.
1 <?php
2 
5 
9 class SimpleCaptcha {
10  protected static $messagePrefix = 'captcha-';
11 
13  private $captchaSolved = null;
14 
20  protected $action;
21 
23  protected $trigger;
24 
28  public function setAction( $action ) {
29  $this->action = $action;
30  }
31 
35  public function setTrigger( $trigger ) {
36  $this->trigger = $trigger;
37  }
38 
44  public function getError() {
45  return null;
46  }
47 
54  function getCaptcha() {
55  $a = mt_rand( 0, 100 );
56  $b = mt_rand( 0, 10 );
57 
58  /* Minus sign is used in the question. UTF-8,
59  since the api uses text/plain, not text/html */
60  $op = mt_rand( 0, 1 ) ? '+' : '−';
61 
62  // No space before and after $op, to ensure correct
63  // directionality.
64  $test = "$a$op$b";
65  $answer = ( $op == '+' ) ? ( $a + $b ) : ( $a - $b );
66  return [ 'question' => $test, 'answer' => $answer ];
67  }
68 
72  function addCaptchaAPI( &$resultArr ) {
73  $captcha = $this->getCaptcha();
74  $index = $this->storeCaptcha( $captcha );
75  $resultArr['captcha'] = $this->describeCaptchaType();
76  $resultArr['captcha']['id'] = $index;
77  $resultArr['captcha']['question'] = $captcha['question'];
78  }
79 
85  public function describeCaptchaType() {
86  return [
87  'type' => 'simple',
88  'mime' => 'text/plain',
89  ];
90  }
91 
121  public function getFormInformation( $tabIndex = 1 ) {
122  $captcha = $this->getCaptcha();
123  $index = $this->storeCaptcha( $captcha );
124 
125  return [
126  'html' => "<p><label for=\"wpCaptchaWord\">{$captcha['question']} = </label>" .
127  Xml::element( 'input', [
128  'name' => 'wpCaptchaWord',
129  'class' => 'mw-ui-input',
130  'id' => 'wpCaptchaWord',
131  'size' => 5,
132  'autocomplete' => 'off',
133  'tabindex' => $tabIndex ] ) . // tab in before the edit textarea
134  "</p>\n" .
135  Xml::element( 'input', [
136  'type' => 'hidden',
137  'name' => 'wpCaptchaId',
138  'id' => 'wpCaptchaId',
139  'value' => $index ] )
140  ];
141  }
142 
150  public function addFormToOutput( OutputPage $out, $tabIndex = 1 ) {
151  $this->addFormInformationToOutput( $out, $this->getFormInformation( $tabIndex ) );
152  }
153 
161  public function addFormInformationToOutput( OutputPage $out, array $formInformation ) {
162  if ( !$formInformation ) {
163  return;
164  }
165  if ( isset( $formInformation['html'] ) ) {
166  $out->addHTML( $formInformation['html'] );
167  }
168  if ( isset( $formInformation['modules'] ) ) {
169  $out->addModules( $formInformation['modules'] );
170  }
171  if ( isset( $formInformation['modulestyles'] ) ) {
172  $out->addModuleStyles( $formInformation['modulestyles'] );
173  }
174  if ( isset( $formInformation['headitems'] ) ) {
175  $out->addHeadItems( $formInformation['headitems'] );
176  }
177  }
178 
184  public function getCaptchaInfo( $captchaData, $id ) {
185  return $captchaData['question'] . ' =';
186  }
187 
193  function showEditFormFields( &$editPage, &$out ) {
194  $page = $editPage->getArticle()->getPage();
195  if ( !isset( $page->ConfirmEdit_ActivateCaptcha ) ) {
196  return;
197  }
198 
199  if ( $this->action !== 'edit' ) {
200  unset( $page->ConfirmEdit_ActivateCaptcha );
201  $out->addWikiText( $this->getMessage( $this->action )->text() );
202  $this->addFormToOutput( $out );
203  }
204  }
205 
210  function editShowCaptcha( $editPage ) {
211  $context = $editPage->getArticle()->getContext();
212  $page = $editPage->getArticle()->getPage();
213  $out = $context->getOutput();
214  if ( isset( $page->ConfirmEdit_ActivateCaptcha ) ||
215  $this->shouldCheck( $page, '', '', $context )
216  ) {
217  $out->addWikiText( $this->getMessage( $this->action )->text() );
218  $this->addFormToOutput( $out );
219  }
220  unset( $page->ConfirmEdit_ActivateCaptcha );
221  }
222 
230  public function getMessage( $action ) {
231  // one of captcha-edit, captcha-addurl, captcha-badlogin, captcha-createaccount,
232  // captcha-create, captcha-sendemail
233  $name = static::$messagePrefix . $action;
234  $msg = wfMessage( $name );
235  // obtain a more tailored message, if possible, otherwise, fall back to
236  // the default for edits
237  return $msg->isDisabled() ? wfMessage( static::$messagePrefix . 'edit' ) : $msg;
238  }
239 
246  function injectEmailUser( &$form ) {
247  global $wgCaptchaTriggers;
248  $out = $form->getOutput();
249  $user = $form->getUser();
250  if ( $wgCaptchaTriggers['sendemail'] ) {
251  $this->action = 'sendemail';
252  if ( $user->isAllowed( 'skipcaptcha' ) ) {
253  wfDebug( "ConfirmEdit: user group allows skipping captcha on email sending\n" );
254  return true;
255  }
256  $formInformation = $this->getFormInformation();
257  $formMetainfo = $formInformation;
258  unset( $formMetainfo['html'] );
259  $this->addFormInformationToOutput( $out, $formMetainfo );
260  $form->addFooterText(
261  "<div class='captcha'>" .
262  $out->parse( $this->getMessage( 'sendemail' )->text() ) .
263  $formInformation['html'] .
264  "</div>\n" );
265  }
266  return true;
267  }
268 
275  public function increaseBadLoginCounter( $username ) {
276  global $wgCaptchaTriggers, $wgCaptchaBadLoginExpiration,
277  $wgCaptchaBadLoginPerUserExpiration;
279 
280  if ( $wgCaptchaTriggers['badlogin'] ) {
281  $key = $this->badLoginKey();
282  $count = ObjectCache::getLocalClusterInstance()->get( $key );
283  if ( !$count ) {
284  $cache->add( $key, 0, $wgCaptchaBadLoginExpiration );
285  }
286 
287  $cache->incr( $key );
288  }
289 
290  if ( $wgCaptchaTriggers['badloginperuser'] && $username ) {
291  $key = $this->badLoginPerUserKey( $username );
292  $count = $cache->get( $key );
293  if ( !$count ) {
294  $cache->add( $key, 0, $wgCaptchaBadLoginPerUserExpiration );
295  }
296 
297  $cache->incr( $key );
298  }
299  }
300 
305  public function resetBadLoginCounter( $username ) {
306  global $wgCaptchaTriggers;
307 
308  if ( $wgCaptchaTriggers['badloginperuser'] && $username ) {
310  $cache->delete( $this->badLoginPerUserKey( $username ) );
311  }
312  }
313 
320  public function isBadLoginTriggered() {
321  global $wgCaptchaTriggers, $wgCaptchaBadLoginAttempts;
323  return $wgCaptchaTriggers['badlogin']
324  && (int)$cache->get( $this->badLoginKey() ) >= $wgCaptchaBadLoginAttempts;
325  }
326 
333  public function isBadLoginPerUserTriggered( $u ) {
334  global $wgCaptchaTriggers, $wgCaptchaBadLoginPerUserAttempts;
336 
337  if ( is_object( $u ) ) {
338  $u = $u->getName();
339  }
340  return $wgCaptchaTriggers['badloginperuser']
341  && (int)$cache->get( $this->badLoginPerUserKey( $u ) ) >= $wgCaptchaBadLoginPerUserAttempts;
342  }
343 
352  function isIPWhitelisted() {
353  global $wgCaptchaWhitelistIP, $wgRequest;
354  $ip = $wgRequest->getIP();
355 
356  if ( $wgCaptchaWhitelistIP ) {
357  if ( IP::isInRanges( $ip, $wgCaptchaWhitelistIP ) ) {
358  return true;
359  }
360  }
361 
362  $whitelistMsg = wfMessage( 'captcha-ip-whitelist' )->inContentLanguage();
363  if ( !$whitelistMsg->isDisabled() ) {
364  $whitelistedIPs = $this->getWikiIPWhitelist( $whitelistMsg );
365  if ( IP::isInRanges( $ip, $whitelistedIPs ) ) {
366  return true;
367  }
368  }
369 
370  return false;
371  }
372 
380  private function getWikiIPWhitelist( Message $msg ) {
382  $cacheKey = $cache->makeKey( 'confirmedit', 'ipwhitelist' );
383 
384  $cachedWhitelist = $cache->get( $cacheKey );
385  if ( $cachedWhitelist === false ) {
386  // Could not retrieve from cache so build the whitelist directly
387  // from the wikipage
388  $whitelist = $this->buildValidIPs(
389  explode( "\n", $msg->plain() )
390  );
391  // And then store it in cache for one day. This cache is cleared on
392  // modifications to the whitelist page.
393  // @see ConfirmEditHooks::onPageContentSaveComplete()
394  $cache->set( $cacheKey, $whitelist, 86400 );
395  } else {
396  // Whitelist from the cache
397  $whitelist = $cachedWhitelist;
398  }
399 
400  return $whitelist;
401  }
402 
414  private function buildValidIPs( array $input ) {
415  // Remove whitespace and blank lines first
416  $ips = array_map( 'trim', $input );
417  $ips = array_filter( $ips );
418 
419  $validIPs = [];
420  foreach ( $ips as $ip ) {
421  if ( IP::isIPAddress( $ip ) ) {
422  $validIPs[] = $ip;
423  }
424  }
425 
426  return $validIPs;
427  }
428 
433  private function badLoginKey() {
435  $ip = $wgRequest->getIP();
436  return wfGlobalCacheKey( 'captcha', 'badlogin', 'ip', $ip );
437  }
438 
444  private function badLoginPerUserKey( $username ) {
446  return wfGlobalCacheKey( 'captcha', 'badlogin', 'user', md5( $username ) );
447  }
448 
459  function keyMatch( $answer, $info ) {
460  return $answer == $info['answer'];
461  }
462 
463  // ----------------------------------
464 
471  global $wgCaptchaTriggers, $wgCaptchaTriggersOnNamespace;
472  // Special config for this NS?
473  if ( isset( $wgCaptchaTriggersOnNamespace[$title->getNamespace()][$action] ) ) {
474  return $wgCaptchaTriggersOnNamespace[$title->getNamespace()][$action];
475  }
476 
477  return ( !empty( $wgCaptchaTriggers[$action] ) ); // Default
478  }
479 
489  function shouldCheck( WikiPage $page, $content, $section, $context, $oldtext = null ) {
490  // @codingStandardsIgnoreStart
491  global $ceAllowConfirmedEmail;
492  // @codingStandardsIgnoreEnd
493 
494  if ( !$context instanceof IContextSource ) {
496  }
497 
499  $user = $context->getUser();
500 
501  // captcha check exceptions, which will return always false
502  if ( $user->isAllowed( 'skipcaptcha' ) ) {
503  wfDebug( "ConfirmEdit: user group allows skipping captcha\n" );
504  return false;
505  } elseif ( $this->isIPWhitelisted() ) {
506  wfDebug( "ConfirmEdit: user IP is whitelisted" );
507  return false;
508  } elseif ( $ceAllowConfirmedEmail && $user->isEmailConfirmed() ) {
509  wfDebug( "ConfirmEdit: user has confirmed mail, skipping captcha\n" );
510  return false;
511  }
512 
513  $title = $page->getTitle();
514  $this->trigger = '';
515 
516  if ( $content instanceof Content ) {
517  if ( $content->getModel() == CONTENT_MODEL_WIKITEXT ) {
518  $newtext = $content->getNativeData();
519  } else {
520  $newtext = null;
521  }
522  $isEmpty = $content->isEmpty();
523  } else {
524  $newtext = $content;
525  $isEmpty = $content === '';
526  }
527 
528  if ( $this->captchaTriggers( $title, 'edit' ) ) {
529  // Check on all edits
530  $this->trigger = sprintf( "edit trigger by '%s' at [[%s]]",
531  $user->getName(),
532  $title->getPrefixedText() );
533  $this->action = 'edit';
534  wfDebug( "ConfirmEdit: checking all edits...\n" );
535  return true;
536  }
537 
538  if ( $this->captchaTriggers( $title, 'create' ) && !$title->exists() ) {
539  // Check if creating a page
540  $this->trigger = sprintf( "Create trigger by '%s' at [[%s]]",
541  $user->getName(),
542  $title->getPrefixedText() );
543  $this->action = 'create';
544  wfDebug( "ConfirmEdit: checking on page creation...\n" );
545  return true;
546  }
547 
548  // The following checks are expensive and should be done only,
549  // if we can assume, that the edit will be saved
550  if ( !$request->wasPosted() ) {
551  wfDebug(
552  "ConfirmEdit: request not posted, assuming that no content will be saved -> no CAPTCHA check"
553  );
554  return false;
555  }
556 
557  if ( !$isEmpty && $this->captchaTriggers( $title, 'addurl' ) ) {
558  // Only check edits that add URLs
559  if ( $content instanceof Content ) {
560  // Get links from the database
561  $oldLinks = $this->getLinksFromTracker( $title );
562  // Share a parse operation with Article::doEdit()
563  $editInfo = $page->prepareContentForEdit( $content );
564  if ( $editInfo->output ) {
565  $newLinks = array_keys( $editInfo->output->getExternalLinks() );
566  } else {
567  $newLinks = [];
568  }
569  } else {
570  // Get link changes in the slowest way known to man
571  $oldtext = isset( $oldtext ) ? $oldtext : $this->loadText( $title, $section );
572  $oldLinks = $this->findLinks( $title, $oldtext );
573  $newLinks = $this->findLinks( $title, $newtext );
574  }
575 
576  $unknownLinks = array_filter( $newLinks, [ $this, 'filterLink' ] );
577  $addedLinks = array_diff( $unknownLinks, $oldLinks );
578  $numLinks = count( $addedLinks );
579 
580  if ( $numLinks > 0 ) {
581  $this->trigger = sprintf( "%dx url trigger by '%s' at [[%s]]: %s",
582  $numLinks,
583  $user->getName(),
584  $title->getPrefixedText(),
585  implode( ", ", $addedLinks ) );
586  $this->action = 'addurl';
587  return true;
588  }
589  }
590 
591  global $wgCaptchaRegexes;
592  if ( $newtext !== null && $wgCaptchaRegexes ) {
593  if ( !is_array( $wgCaptchaRegexes ) ) {
594  throw new UnexpectedValueException(
595  '$wgCaptchaRegexes is required to be an array, ' . gettype( $wgCaptchaRegexes ) . ' given.'
596  );
597  }
598  // Custom regex checks. Reuse $oldtext if set above.
599  $oldtext = isset( $oldtext ) ? $oldtext : $this->loadText( $title, $section );
600 
601  foreach ( $wgCaptchaRegexes as $regex ) {
602  $newMatches = [];
603  if ( preg_match_all( $regex, $newtext, $newMatches ) ) {
604  $oldMatches = [];
605  preg_match_all( $regex, $oldtext, $oldMatches );
606 
607  $addedMatches = array_diff( $newMatches[0], $oldMatches[0] );
608 
609  $numHits = count( $addedMatches );
610  if ( $numHits > 0 ) {
611  $this->trigger = sprintf( "%dx %s at [[%s]]: %s",
612  $numHits,
613  $regex,
614  $user->getName(),
615  $title->getPrefixedText(),
616  implode( ", ", $addedMatches ) );
617  $this->action = 'edit';
618  return true;
619  }
620  }
621  }
622  }
623 
624  return false;
625  }
626 
633  function filterLink( $url ) {
634  global $wgCaptchaWhitelist;
635  static $regexes = null;
636 
637  if ( $regexes === null ) {
638  $source = wfMessage( 'captcha-addurl-whitelist' )->inContentLanguage();
639 
640  $regexes = $source->isDisabled()
641  ? []
642  : $this->buildRegexes( explode( "\n", $source->plain() ) );
643 
644  if ( $wgCaptchaWhitelist !== false ) {
645  array_unshift( $regexes, $wgCaptchaWhitelist );
646  }
647  }
648 
649  foreach ( $regexes as $regex ) {
650  if ( preg_match( $regex, $url ) ) {
651  return false;
652  }
653  }
654 
655  return true;
656  }
657 
664  function buildRegexes( $lines ) {
665  # Code duplicated from the SpamBlacklist extension (r19197)
666  # and later modified.
667 
668  # Strip comments and whitespace, then remove blanks
669  $lines = array_filter( array_map( 'trim', preg_replace( '/#.*$/', '', $lines ) ) );
670 
671  # No lines, don't make a regex which will match everything
672  if ( count( $lines ) == 0 ) {
673  wfDebug( "No lines\n" );
674  return [];
675  } else {
676  # Make regex
677  # It's faster using the S modifier even though it will usually only be run once
678  // $regex = 'http://+[a-z0-9_\-.]*(' . implode( '|', $lines ) . ')';
679  // return '/' . str_replace( '/', '\/', preg_replace('|\\\*/|', '/', $regex) ) . '/Si';
680  $regexes = [];
681  $regexStart = [
682  'normal' => '/^(?:https?:)?\/\/+[a-z0-9_\-.]*(?:',
683  'noprotocol' => '/^(?:',
684  ];
685  $regexEnd = [
686  'normal' => ')/Si',
687  'noprotocol' => ')/Si',
688  ];
689  $regexMax = 4096;
690  $build = [];
691  foreach ( $lines as $line ) {
692  # Extract flags from the line
693  $options = [];
694  if ( preg_match( '/^(.*?)\s*<([^<>]*)>$/', $line, $matches ) ) {
695  if ( $matches[1] === '' ) {
696  wfDebug( "Line with empty regex\n" );
697  continue;
698  }
699  $line = $matches[1];
700  $opts = preg_split( '/\s*\|\s*/', trim( $matches[2] ) );
701  foreach ( $opts as $opt ) {
702  $opt = strtolower( $opt );
703  if ( $opt == 'noprotocol' ) {
704  $options['noprotocol'] = true;
705  }
706  }
707  }
708 
709  $key = isset( $options['noprotocol'] ) ? 'noprotocol' : 'normal';
710 
711  // FIXME: not very robust size check, but should work. :)
712  if ( !isset( $build[$key] ) ) {
713  $build[$key] = $line;
714  } elseif ( strlen( $build[$key] ) + strlen( $line ) > $regexMax ) {
715  $regexes[] = $regexStart[$key] .
716  str_replace( '/', '\/', preg_replace( '|\\\*/|', '/', $build[$key] ) ) .
717  $regexEnd[$key];
718  $build[$key] = $line;
719  } else {
720  $build[$key] .= '|' . $line;
721  }
722  }
723  foreach ( $build as $key => $value ) {
724  $regexes[] = $regexStart[$key] .
725  str_replace( '/', '\/', preg_replace( '|\\\*/|', '/', $build[$key] ) ) .
726  $regexEnd[$key];
727  }
728  return $regexes;
729  }
730  }
731 
738  $dbr = wfGetDB( DB_SLAVE );
739  $id = $title->getArticleID(); // should be zero queries
740  $res = $dbr->select( 'externallinks', [ 'el_to' ],
741  [ 'el_from' => $id ], __METHOD__ );
742  $links = [];
743  foreach ( $res as $row ) {
744  $links[] = $row->el_to;
745  }
746  return $links;
747  }
748 
757  private function doConfirmEdit( WikiPage $page, $newtext, $section, IContextSource $context ) {
760 
761  // FIXME: Stop using wgRequest in other parts of ConfirmEdit so we can
762  // stop having to duplicate code for it.
763  if ( $request->getVal( 'captchaid' ) ) {
764  $request->setVal( 'wpCaptchaId', $request->getVal( 'captchaid' ) );
765  $wgRequest->setVal( 'wpCaptchaId', $request->getVal( 'captchaid' ) );
766  }
767  if ( $request->getVal( 'captchaword' ) ) {
768  $request->setVal( 'wpCaptchaWord', $request->getVal( 'captchaword' ) );
769  $wgRequest->setVal( 'wpCaptchaWord', $request->getVal( 'captchaword' ) );
770  }
771  if ( $this->shouldCheck( $page, $newtext, $section, $context ) ) {
772  return $this->passCaptchaLimitedFromRequest( $wgRequest, $wgUser );
773  } else {
774  wfDebug( "ConfirmEdit: no need to show captcha.\n" );
775  return true;
776  }
777  }
778 
789  function confirmEditMerged( $context, $content, $status, $summary, $user, $minorEdit ) {
790  if ( !$context->canUseWikiPage() ) {
791  // we check WikiPage only
792  // try to get an appropriate title for this page
793  $title = $context->getTitle();
794  if ( $title instanceof Title ) {
795  $title = $title->getFullText();
796  } else {
797  // otherwise it's an unknown page where this function is called from
798  $title = 'unknown';
799  }
800  // log this error, it could be a problem in another extension,
801  // edits should always have a WikiPage if
802  // they go through EditFilterMergedContent.
803  wfDebug( __METHOD__ . ': Skipped ConfirmEdit check: No WikiPage for title ' . $title );
804  return true;
805  }
807  if ( !$this->doConfirmEdit( $page, $content, false, $context ) ) {
809  $status->apiHookResult = [];
810  // give an error message for the user to know, what goes wrong here.
811  // this can't be done for addurl trigger, because this requires one "free" save
812  // for the user, which we don't know, when he did it.
813  if ( $this->action === 'edit' ) {
814  $status->fatal(
815  new RawMessage(
817  'div',
818  [ 'class' => 'errorbox' ],
819  $context->msg( 'captcha-edit-fail' )->text()
820  )
821  )
822  );
823  }
824  $this->addCaptchaAPI( $status->apiHookResult );
825  $page->ConfirmEdit_ActivateCaptcha = true;
826  return false;
827  }
828  return true;
829  }
830 
838  public function needCreateAccountCaptcha( User $creatingUser = null ) {
839  global $wgCaptchaTriggers, $wgUser;
840  $creatingUser = $creatingUser ?: $wgUser;
841 
842  if ( $wgCaptchaTriggers['createaccount'] ) {
843  if ( $creatingUser->isAllowed( 'skipcaptcha' ) ) {
844  wfDebug( "ConfirmEdit: user group allows skipping captcha on account creation\n" );
845  return false;
846  }
847  if ( $this->isIPWhitelisted() ) {
848  return false;
849  }
850  return true;
851  }
852  return false;
853  }
854 
864  function confirmEmailUser( $from, $to, $subject, $text, &$error ) {
865  global $wgCaptchaTriggers, $wgUser, $wgRequest;
866 
867  if ( $wgCaptchaTriggers['sendemail'] ) {
868  if ( $wgUser->isAllowed( 'skipcaptcha' ) ) {
869  wfDebug( "ConfirmEdit: user group allows skipping captcha on email sending\n" );
870  return true;
871  }
872  if ( $this->isIPWhitelisted() ) {
873  return true;
874  }
875 
876  if ( defined( 'MW_API' ) ) {
877  # API mode
878  # Asking for captchas in the API is really silly
879  $error = Status::newFatal( 'captcha-disabledinapi' );
880  return false;
881  }
882  $this->trigger = "{$wgUser->getName()} sending email";
883  if ( !$this->passCaptchaLimitedFromRequest( $wgRequest, $wgUser ) ) {
884  $error = Status::newFatal( 'captcha-sendemail-fail' );
885  return false;
886  }
887  }
888  return true;
889  }
890 
895  protected function isAPICaptchaModule( $module ) {
896  return $module instanceof ApiEditPage;
897  }
898 
905  public function APIGetAllowedParams( &$module, &$params, $flags ) {
906  if ( $this->isAPICaptchaModule( $module ) ) {
907  $params['captchaword'] = [
908  ApiBase::PARAM_HELP_MSG => 'captcha-apihelp-param-captchaword',
909  ];
910  $params['captchaid'] = [
911  ApiBase::PARAM_HELP_MSG => 'captcha-apihelp-param-captchaid',
912  ];
913  }
914 
915  return true;
916  }
917 
927  list( $index, $word ) = $this->getCaptchaParamsFromRequest( $request );
928  return $this->passCaptchaLimited( $index, $word, $user );
929  }
930 
936  $index = $request->getVal( 'wpCaptchaId' );
937  $word = $request->getVal( 'wpCaptchaWord' );
938  return [ $index, $word ];
939  }
940 
951  public function passCaptchaLimited( $index, $word, User $user ) {
952  // don't increase pingLimiter here, just check, if CAPTCHA limit exceeded
953  if ( $user->pingLimiter( 'badcaptcha', 0 ) ) {
954  // for debugging add an proper error message, the user just see an false captcha error message
955  $this->log( 'User reached RateLimit, preventing action' );
956  return false;
957  }
958 
959  if ( $this->passCaptcha( $index, $word ) ) {
960  return true;
961  }
962 
963  // captcha was not solved: increase limit and return false
964  $user->pingLimiter( 'badcaptcha' );
965  return false;
966  }
967 
976  list( $index, $word ) = $this->getCaptchaParamsFromRequest( $request );
977  return $this->passCaptcha( $index, $word );
978  }
979 
987  protected function passCaptcha( $index, $word ) {
988  // Don't check the same CAPTCHA twice in one session,
989  // if the CAPTCHA was already checked - Bug T94276
990  if ( isset( $this->captchaSolved ) ) {
991  return $this->captchaSolved;
992  }
993 
994  $info = $this->retrieveCaptcha( $index );
995  if ( $info ) {
996  if ( $this->keyMatch( $word, $info ) ) {
997  $this->log( "passed" );
998  $this->clearCaptcha( $index );
999  $this->captchaSolved = true;
1000  return true;
1001  } else {
1002  $this->clearCaptcha( $index );
1003  $this->log( "bad form input" );
1004  $this->captchaSolved = false;
1005  return false;
1006  }
1007  } else {
1008  $this->log( "new captcha session" );
1009  return false;
1010  }
1011  }
1012 
1017  function log( $message ) {
1018  wfDebugLog( 'captcha', 'ConfirmEdit: ' . $message . '; ' . $this->trigger );
1019  }
1020 
1032  public function storeCaptcha( $info ) {
1033  if ( !isset( $info['index'] ) ) {
1034  // Assign random index if we're not udpating
1035  $info['index'] = strval( mt_rand() );
1036  }
1037  CaptchaStore::get()->store( $info['index'], $info );
1038  return $info['index'];
1039  }
1040 
1046  public function retrieveCaptcha( $index ) {
1047  return CaptchaStore::get()->retrieve( $index );
1048  }
1049 
1054  public function clearCaptcha( $index ) {
1055  CaptchaStore::get()->clear( $index );
1056  }
1057 
1066  function loadText( $title, $section, $flags = Revision::READ_LATEST ) {
1067  global $wgParser;
1068 
1070  if ( is_null( $rev ) ) {
1071  return "";
1072  }
1073 
1074  $content = $rev->getContent();
1076  if ( $section !== '' ) {
1077  return $wgParser->getSection( $text, $section );
1078  }
1079 
1080  return $text;
1081  }
1082 
1089  function findLinks( $title, $text ) {
1091 
1092  $options = new ParserOptions();
1093  $text = $wgParser->preSaveTransform( $text, $title, $wgUser, $options );
1094  $out = $wgParser->parse( $text, $title, $options );
1095 
1096  return array_keys( $out->getExternalLinks() );
1097  }
1098 
1102  function showHelp() {
1103  global $wgOut;
1104  $wgOut->setPageTitle( wfMessage( 'captchahelp-title' )->text() );
1105  $wgOut->addWikiMsg( 'captchahelp-text' );
1106  if ( CaptchaStore::get()->cookiesNeeded() ) {
1107  $wgOut->addWikiMsg( 'captchahelp-cookies-needed' );
1108  }
1109  }
1110 
1114  public function createAuthenticationRequest() {
1115  $captchaData = $this->getCaptcha();
1116  $id = $this->storeCaptcha( $captchaData );
1117  return new CaptchaAuthenticationRequest( $id, $captchaData );
1118  }
1119 
1127  public function onAuthChangeFormFields(
1128  array $requests, array $fieldInfo, array &$formDescriptor, $action
1129  ) {
1130  $req = AuthenticationRequest::getRequestByClass( $requests,
1132  if ( !$req ) {
1133  return;
1134  }
1135 
1136  $formDescriptor['captchaWord'] = [
1137  'label-message' => null,
1138  'autocomplete' => false,
1139  'persistent' => false,
1140  'required' => true,
1141  ] + $formDescriptor['captchaWord'];
1142  }
1143 }
ApiEditPage
A module that allows for editing and creating pages.
Definition: ApiEditPage.php:36
SimpleCaptcha\confirmEmailUser
confirmEmailUser( $from, $to, $subject, $text, &$error)
Check the captcha on Special:EmailUser.
Definition: Captcha.php:864
ParserOptions
Set options of the Parser.
Definition: ParserOptions.php:33
CaptchaStore\get
static get()
Get somewhere to store captcha data that will persist between requests.
Definition: CaptchaStore.php:44
$wgUser
$wgUser
Definition: Setup.php:781
$context
error also a ContextSource you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition: hooks.txt:2612
ContextSource\getContext
getContext()
Get the base IContextSource object.
Definition: ContextSource.php:41
SimpleCaptcha\badLoginPerUserKey
badLoginPerUserKey( $username)
Cache key for badloginPerUser checks.
Definition: Captcha.php:444
$request
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2612
ObjectCache\getLocalClusterInstance
static getLocalClusterInstance()
Get the main cluster-local cache object.
Definition: ObjectCache.php:357
$wgParser
$wgParser
Definition: Setup.php:796
SimpleCaptcha\loadText
loadText( $title, $section, $flags=Revision::READ_LATEST)
Retrieve the current version of the page or section being edited...
Definition: Captcha.php:1066
ContextSource\msg
msg()
Get a Message object with context set Parameters are the same as wfMessage()
Definition: ContextSource.php:187
SimpleCaptcha\passCaptchaLimitedFromRequest
passCaptchaLimitedFromRequest(WebRequest $request, User $user)
Checks, if the user reached the amount of false CAPTCHAs and give him some vacation or run self::pass...
Definition: Captcha.php:926
IP\isInRanges
static isInRanges( $ip, $ranges)
Determines if an IP address is a list of CIDR a.b.c.d/n ranges.
Definition: IP.php:656
$opt
$opt
Definition: postprocess-phan.php:115
captcha-old.count
count
Definition: captcha-old.py:225
SimpleCaptcha\addFormToOutput
addFormToOutput(OutputPage $out, $tabIndex=1)
Uses getFormInformation() to get the CAPTCHA form and adds it to the given OutputPage object.
Definition: Captcha.php:150
text
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:12
$regexes
if( $wgSpamBlacklistFiles) $regexes
Definition: cleanup.php:88
ApiBase\PARAM_HELP_MSG
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition: ApiBase.php:128
$status
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition: hooks.txt:1049
SimpleCaptcha\keyMatch
keyMatch( $answer, $info)
Check if the submitted form matches the captcha session data provided by the plugin when the form was...
Definition: Captcha.php:459
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
SimpleCaptcha\editShowCaptcha
editShowCaptcha( $editPage)
Insert the captcha prompt into an edit form.
Definition: Captcha.php:210
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:246
$req
this hook is for auditing only $req
Definition: hooks.txt:990
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:36
StatusValue\newFatal
static newFatal( $message)
Factory function for fatal errors.
Definition: StatusValue.php:63
$params
$params
Definition: styleTest.css.php:40
SimpleCaptcha\captchaTriggers
captchaTriggers( $title, $action)
Definition: Captcha.php:470
DB_SLAVE
const DB_SLAVE
Definition: Defines.php:34
ContextSource\canUseWikiPage
canUseWikiPage()
Check whether a WikiPage object can be get with getWikiPage().
Definition: ContextSource.php:100
$res
$res
Definition: database.txt:21
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:304
SimpleCaptcha\$action
string $action
Used to select the right message.
Definition: Captcha.php:20
ContextSource\getRequest
getRequest()
Get the WebRequest object.
Definition: ContextSource.php:78
CONTENT_MODEL_WIKITEXT
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:233
SimpleCaptcha\showHelp
showHelp()
Show a page explaining what this wacky thing is.
Definition: Captcha.php:1102
ContextSource\getUser
getUser()
Get the User object.
Definition: ContextSource.php:133
ContextSource\getTitle
getTitle()
Get the Title object.
Definition: ContextSource.php:88
SimpleCaptcha\setAction
setAction( $action)
Definition: Captcha.php:28
SimpleCaptcha\injectEmailUser
injectEmailUser(&$form)
Inject whazawhoo @fixme if multiple thingies insert a header, could break.
Definition: Captcha.php:246
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1092
SimpleCaptcha\buildRegexes
buildRegexes( $lines)
Build regex from whitelist.
Definition: Captcha.php:664
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
SimpleCaptcha\getCaptchaInfo
getCaptchaInfo( $captchaData, $id)
Definition: Captcha.php:184
Revision\newFromTitle
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target.
Definition: Revision.php:134
SimpleCaptcha\passCaptchaLimited
passCaptchaLimited( $index, $word, User $user)
Checks, if the user reached the amount of false CAPTCHAs and give him some vacation or run self::pass...
Definition: Captcha.php:951
SimpleCaptcha\addCaptchaAPI
addCaptchaAPI(&$resultArr)
Definition: Captcha.php:72
SimpleCaptcha\resetBadLoginCounter
resetBadLoginCounter( $username)
Reset bad login counter after a successful login.
Definition: Captcha.php:305
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:934
wfGlobalCacheKey
wfGlobalCacheKey()
Make a cache key with database-agnostic prefix.
Definition: GlobalFunctions.php:2998
SimpleCaptcha\getCaptcha
getCaptcha()
Returns an array with 'question' and 'answer' keys.
Definition: Captcha.php:54
SimpleCaptcha\filterLink
filterLink( $url)
Filter callback function for URL whitelisting.
Definition: Captcha.php:633
$content
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content $content
Definition: hooks.txt:1049
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:3060
$input
if(is_array( $mode)) switch( $mode) $input
Definition: postprocess-phan.php:141
$page
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached $page
Definition: hooks.txt:2536
ContextSource\getOutput
getOutput()
Get the OutputPage object.
Definition: ContextSource.php:123
$matches
$matches
Definition: NoLocalSettings.php:24
SimpleCaptcha\findLinks
findLinks( $title, $text)
Extract a list of all recognized HTTP links in the text.
Definition: Captcha.php:1089
SimpleCaptcha\getWikiIPWhitelist
getWikiIPWhitelist(Message $msg)
Get the on-wiki IP whitelist stored in [[MediaWiki:Captcha-ip-whitelist]] page from cache if possible...
Definition: Captcha.php:380
ContextSource\getWikiPage
getWikiPage()
Get the WikiPage object.
Definition: ContextSource.php:113
Xml\element
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:39
SimpleCaptcha\getCaptchaParamsFromRequest
getCaptchaParamsFromRequest(WebRequest $request)
Definition: Captcha.php:935
$lines
$lines
Definition: router.php:67
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
SimpleCaptcha\onAuthChangeFormFields
onAuthChangeFormFields(array $requests, array $fieldInfo, array &$formDescriptor, $action)
Modify the apprearance of the captcha field.
Definition: Captcha.php:1127
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:999
OutputPage
This class should be covered by a general architecture document which does not exist as of January 20...
Definition: OutputPage.php:44
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
captcha-old.action
action
Definition: captcha-old.py:189
SimpleCaptcha\doConfirmEdit
doConfirmEdit(WikiPage $page, $newtext, $section, IContextSource $context)
Backend function for confirmEditMerged()
Definition: Captcha.php:757
SimpleCaptcha\badLoginKey
badLoginKey()
Internal cache key for badlogin checks.
Definition: Captcha.php:433
$line
$line
Definition: cdb.php:58
SimpleCaptcha\confirmEditMerged
confirmEditMerged( $context, $content, $status, $summary, $user, $minorEdit)
An efficient edit filter callback based on the text after section merging.
Definition: Captcha.php:789
SimpleCaptcha\isIPWhitelisted
isIPWhitelisted()
Check if the current IP is allowed to skip captchas.
Definition: Captcha.php:352
SimpleCaptcha\isBadLoginTriggered
isBadLoginTriggered()
Check if a bad login has already been registered for this IP address.
Definition: Captcha.php:320
$value
$value
Definition: styleTest.css.php:45
SimpleCaptcha\setTrigger
setTrigger( $trigger)
Definition: Captcha.php:35
SimpleCaptcha\log
log( $message)
Log the status and any triggering info for debugging or statistics.
Definition: Captcha.php:1017
SimpleCaptcha\retrieveCaptcha
retrieveCaptcha( $index)
Fetch this session's captcha info.
Definition: Captcha.php:1046
SimpleCaptcha\createAuthenticationRequest
createAuthenticationRequest()
Definition: Captcha.php:1114
SimpleCaptcha\needCreateAccountCaptcha
needCreateAccountCaptcha(User $creatingUser=null)
Logic to check if we need to pass a captcha for the current user to create a new account,...
Definition: Captcha.php:838
SimpleCaptcha\$messagePrefix
static $messagePrefix
Definition: Captcha.php:10
SimpleCaptcha\addFormInformationToOutput
addFormInformationToOutput(OutputPage $out, array $formInformation)
Processes the given $formInformation array and adds the options (see getFormInformation()) to the giv...
Definition: Captcha.php:161
SimpleCaptcha
Demo CAPTCHA (not for production usage) and base class for real CAPTCHAs.
Definition: Captcha.php:9
SimpleCaptcha\getError
getError()
Return the error from the last passCaptcha* call.
Definition: Captcha.php:44
SimpleCaptcha\storeCaptcha
storeCaptcha( $info)
Generate a captcha session ID and save the info in PHP's session storage.
Definition: Captcha.php:1032
RequestContext\getMain
static getMain()
Static methods.
Definition: RequestContext.php:468
SimpleCaptcha\getMessage
getMessage( $action)
Show a message asking the user to enter a captcha on edit The result will be treated as wiki text.
Definition: Captcha.php:230
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:55
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:38
SimpleCaptcha\describeCaptchaType
describeCaptchaType()
Describes the captcha type for API clients.
Definition: Captcha.php:85
SimpleCaptcha\isBadLoginPerUserTriggered
isBadLoginPerUserTriggered( $u)
Is the per-user captcha triggered?
Definition: Captcha.php:333
Content
Base interface for content objects.
Definition: Content.php:34
SimpleCaptcha\isAPICaptchaModule
isAPICaptchaModule( $module)
Definition: Captcha.php:895
Title
Represents a title within MediaWiki.
Definition: Title.php:39
SimpleCaptcha\$trigger
string $trigger
Used in log messages.
Definition: Captcha.php:23
ContentHandler\getContentText
static getContentText(Content $content=null)
Convenience function for getting flat text from a Content object.
Definition: ContentHandler.php:79
$dbr
if(! $regexes) $dbr
Definition: cleanup.php:94
SimpleCaptcha\shouldCheck
shouldCheck(WikiPage $page, $content, $section, $context, $oldtext=null)
Definition: Captcha.php:489
$cache
$cache
Definition: mcc.php:33
ObjectCache\getMainWANInstance
static getMainWANInstance()
Get the main WAN cache object.
Definition: ObjectCache.php:370
SimpleCaptcha\increaseBadLoginCounter
increaseBadLoginCounter( $username)
Increase bad login counter after a failed login.
Definition: Captcha.php:275
SimpleCaptcha\$captchaSolved
boolean null $captchaSolved
Was the CAPTCHA already passed and if yes, with which result?
Definition: Captcha.php:13
SimpleCaptcha\passCaptcha
passCaptcha( $index, $word)
Given a required captcha run, test form input for correct input on the open session.
Definition: Captcha.php:987
$section
usually copyright or history_copyright This message must be in HTML not wikitext if the section is included from a template $section
Definition: hooks.txt:2929
User\getCanonicalName
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid.
Definition: User.php:1076
SimpleCaptcha\getLinksFromTracker
getLinksFromTracker( $title)
Load external links from the externallinks table.
Definition: Captcha.php:737
$rev
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition: hooks.txt:1741
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
$requests
Allows to change the fields on the form that will be generated are created Can be used to omit specific feeds from being outputted You must not use this hook to add use OutputPage::addFeedLink() instead. & $feedLinks hooks can tweak the array to change how login etc forms should look $requests
Definition: hooks.txt:306
LoggerFactory
MediaWiki Logger LoggerFactory implements a PSR[0] compatible message logging system Named Psr Log LoggerInterface instances can be obtained from the MediaWiki Logger LoggerFactory::getInstance() static method. MediaWiki\Logger\LoggerFactory expects a class implementing the MediaWiki\Logger\Spi interface to act as a factory for new Psr\Log\LoggerInterface instances. The "Spi" in MediaWiki\Logger\Spi stands for "service provider interface". An SPI is an API intended to be implemented or extended by a third party. This software design pattern is intended to enable framework extension and replaceable components. It is specifically used in the MediaWiki\Logger\LoggerFactory service to allow alternate PSR-3 logging implementations to be easily integrated with MediaWiki. The service provider interface allows the backend logging library to be implemented in multiple ways. The $wgMWLoggerDefaultSpi global provides the classname of the default MediaWiki\Logger\Spi implementation to be loaded at runtime. This can either be the name of a class implementing the MediaWiki\Logger\Spi with a zero argument const ructor or a callable that will return an MediaWiki\Logger\Spi instance. Alternately the MediaWiki\Logger\LoggerFactory MediaWiki Logger LoggerFactory
Definition: logger.txt:5
$source
$source
Definition: mwdoc-filter.php:45
wfMessage
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
class
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:639
Html\element
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:231
SimpleCaptcha\APIGetAllowedParams
APIGetAllowedParams(&$module, &$params, $flags)
Definition: Captcha.php:905
$wgOut
$wgOut
Definition: Setup.php:791
SimpleCaptcha\showEditFormFields
showEditFormFields(&$editPage, &$out)
Show error message for missing or incorrect captcha on EditPage.
Definition: Captcha.php:193
SimpleCaptcha\getFormInformation
getFormInformation( $tabIndex=1)
Insert a captcha prompt into the edit form.
Definition: Captcha.php:121
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:50
$username
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:783
SimpleCaptcha\passCaptchaFromRequest
passCaptchaFromRequest(WebRequest $request, User $user)
Given a required captcha run, test form input for correct input on the open session.
Definition: Captcha.php:975
$options
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1049
$flags
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2749
EditPage\AS_HOOK_ERROR_EXPECTED
const AS_HOOK_ERROR_EXPECTED
Status: A hook function returned an error.
Definition: EditPage.php:61
MediaWiki\Auth\AuthenticationRequest
This is a value object for authentication requests.
Definition: AuthenticationRequest.php:37
IP\isIPAddress
static isIPAddress( $ip)
Determine if a string is as valid IP address or network (CIDR prefix).
Definition: IP.php:79
array
the array() calling protocol came about after MediaWiki 1.4rc1.
CaptchaAuthenticationRequest
Generic captcha authentication request class.
Definition: CaptchaAuthenticationRequest.php:10
SimpleCaptcha\buildValidIPs
buildValidIPs(array $input)
From a list of unvalidated input, get all the valid IP addresses and IP ranges from it.
Definition: Captcha.php:414
$out
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition: hooks.txt:783
SimpleCaptcha\clearCaptcha
clearCaptcha( $index)
Clear out existing captcha info from the session, to ensure it can't be reused.
Definition: Captcha.php:1054