[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/extensions/ConfirmEdit/ -> Captcha.php (source)

   1  <?php
   2  
   3  class SimpleCaptcha {
   4  	function getCaptcha() {
   5          $a = mt_rand( 0, 100 );
   6          $b = mt_rand( 0, 10 );
   7  
   8          /* Minus sign is used in the question. UTF-8,
   9             since the api uses text/plain, not text/html */
  10          $op = mt_rand( 0, 1 ) ? '+' : '−';
  11  
  12          // No space before and after $op, to ensure correct
  13          // directionality.
  14          $test = "$a$op$b";
  15          $answer = ( $op == '+' ) ? ( $a + $b ) : ( $a - $b );
  16          return array( 'question' => $test, 'answer' => $answer );
  17      }
  18  
  19  	function addCaptchaAPI( &$resultArr ) {
  20          $captcha = $this->getCaptcha();
  21          $index = $this->storeCaptcha( $captcha );
  22          $resultArr['captcha']['type'] = 'simple';
  23          $resultArr['captcha']['mime'] = 'text/plain';
  24          $resultArr['captcha']['id'] = $index;
  25          $resultArr['captcha']['question'] = $captcha['question'];
  26      }
  27  
  28      /**
  29       * Insert a captcha prompt into the edit form.
  30       * This sample implementation generates a simple arithmetic operation;
  31       * it would be easy to defeat by machine.
  32       *
  33       * Override this!
  34       *
  35       * @return string HTML
  36       */
  37  	function getForm() {
  38          $captcha = $this->getCaptcha();
  39          $index = $this->storeCaptcha( $captcha );
  40  
  41          return "<p><label for=\"wpCaptchaWord\">{$captcha['question']} = </label>" .
  42              Xml::element( 'input', array(
  43                  'name' => 'wpCaptchaWord',
  44                  'class' => 'mw-ui-input',
  45                  'id'   => 'wpCaptchaWord',
  46                  'size'  => 5,
  47                  'autocomplete' => 'off',
  48                  'tabindex' => 1 ) ) . // tab in before the edit textarea
  49              "</p>\n" .
  50              Xml::element( 'input', array(
  51                  'type'  => 'hidden',
  52                  'name'  => 'wpCaptchaId',
  53                  'id'    => 'wpCaptchaId',
  54                  'value' => $index ) );
  55      }
  56  
  57      /**
  58       * Insert the captcha prompt into an edit form.
  59       * @param OutputPage $out
  60       */
  61  	function editCallback( &$out ) {
  62          $out->addWikiText( $this->getMessage( $this->action ) );
  63          $out->addHTML( $this->getForm() );
  64      }
  65  
  66      /**
  67       * Show a message asking the user to enter a captcha on edit
  68       * The result will be treated as wiki text
  69       *
  70       * @param $action string Action being performed
  71       * @return string
  72       */
  73  	function getMessage( $action ) {
  74          $name = 'captcha-' . $action;
  75          $text = wfMessage( $name )->text();
  76          # Obtain a more tailored message, if possible, otherwise, fall back to
  77          # the default for edits
  78          return wfMessage( $name, $text )->isDisabled() ? wfMessage( 'captcha-edit' )->text() : $text;
  79      }
  80  
  81      /**
  82       * Inject whazawhoo
  83       * @fixme if multiple thingies insert a header, could break
  84       * @param $form HTMLForm
  85       * @return bool true to keep running callbacks
  86       */
  87  	function injectEmailUser( &$form ) {
  88          global $wgCaptchaTriggers, $wgOut, $wgUser;
  89          if ( $wgCaptchaTriggers['sendemail'] ) {
  90              if ( $wgUser->isAllowed( 'skipcaptcha' ) ) {
  91                  wfDebug( "ConfirmEdit: user group allows skipping captcha on email sending\n" );
  92                  return true;
  93              }
  94              $form->addFooterText(
  95                  "<div class='captcha'>" .
  96                  $wgOut->parse( $this->getMessage( 'sendemail' ) ) .
  97                  $this->getForm() .
  98                  "</div>\n" );
  99          }
 100          return true;
 101      }
 102  
 103      /**
 104       * Inject whazawhoo
 105       * @fixme if multiple thingies insert a header, could break
 106       * @param QuickTemplate $template
 107       * @return bool true to keep running callbacks
 108       */
 109  	function injectUserCreate( &$template ) {
 110          global $wgCaptchaTriggers, $wgOut, $wgUser;
 111          if ( $wgCaptchaTriggers['createaccount'] ) {
 112              if ( $wgUser->isAllowed( 'skipcaptcha' ) ) {
 113                  wfDebug( "ConfirmEdit: user group allows skipping captcha on account creation\n" );
 114                  return true;
 115              }
 116              $template->set( 'header',
 117                  "<div class='captcha'>" .
 118                  $wgOut->parse( $this->getMessage( 'createaccount' ) ) .
 119                  $this->getForm() .
 120                  "</div>\n" );
 121          }
 122          return true;
 123      }
 124  
 125      /**
 126       * Inject a captcha into the user login form after a failed
 127       * password attempt as a speedbump for mass attacks.
 128       * @fixme if multiple thingies insert a header, could break
 129       * @param $template QuickTemplate
 130       * @return bool true to keep running callbacks
 131       */
 132  	function injectUserLogin( &$template ) {
 133          if ( $this->isBadLoginTriggered() ) {
 134              global $wgOut;
 135              $template->set( 'header',
 136                  "<div class='captcha'>" .
 137                  $wgOut->parse( $this->getMessage( 'badlogin' ) ) .
 138                  $this->getForm() .
 139                  "</div>\n" );
 140          }
 141          return true;
 142      }
 143  
 144      /**
 145       * When a bad login attempt is made, increment an expiring counter
 146       * in the memcache cloud. Later checks for this may trigger a
 147       * captcha display to prevent too many hits from the same place.
 148       * @param User $user
 149       * @param string $password
 150       * @param int $retval authentication return value
 151       * @return bool true to keep running callbacks
 152       */
 153  	function triggerUserLogin( $user, $password, $retval ) {
 154          global $wgCaptchaTriggers, $wgCaptchaBadLoginExpiration, $wgMemc;
 155          if ( $retval == LoginForm::WRONG_PASS && $wgCaptchaTriggers['badlogin'] ) {
 156              $key = $this->badLoginKey();
 157              $count = $wgMemc->get( $key );
 158              if ( !$count ) {
 159                  $wgMemc->add( $key, 0, $wgCaptchaBadLoginExpiration );
 160              }
 161  
 162              $wgMemc->incr( $key );
 163          }
 164          return true;
 165      }
 166  
 167      /**
 168       * Check if a bad login has already been registered for this
 169       * IP address. If so, require a captcha.
 170       * @return bool
 171       * @access private
 172       */
 173  	function isBadLoginTriggered() {
 174          global $wgMemc, $wgCaptchaTriggers, $wgCaptchaBadLoginAttempts;
 175          return $wgCaptchaTriggers['badlogin'] && intval( $wgMemc->get( $this->badLoginKey() ) ) >= $wgCaptchaBadLoginAttempts;
 176      }
 177  
 178      /**
 179       * Check if the IP is allowed to skip captchas
 180       */
 181  	function isIPWhitelisted() {
 182          global $wgCaptchaWhitelistIP;
 183  
 184          if ( $wgCaptchaWhitelistIP ) {
 185              global $wgRequest;
 186  
 187              $ip = $wgRequest->getIP();
 188  
 189              foreach ( $wgCaptchaWhitelistIP as $range ) {
 190                  if ( IP::isInRange( $ip, $range ) ) {
 191                      return true;
 192                  }
 193              }
 194          }
 195          return false;
 196      }
 197  
 198      /**
 199       * Internal cache key for badlogin checks.
 200       * @return string
 201       * @access private
 202       */
 203  	function badLoginKey() {
 204          global $wgRequest;
 205          $ip = $wgRequest->getIP();
 206          return wfMemcKey( 'captcha', 'badlogin', 'ip', $ip );
 207      }
 208  
 209      /**
 210       * Check if the submitted form matches the captcha session data provided
 211       * by the plugin when the form was generated.
 212       *
 213       * Override this!
 214       *
 215       * @param string $answer
 216       * @param array $info
 217       * @return bool
 218       */
 219  	function keyMatch( $answer, $info ) {
 220          return $answer == $info['answer'];
 221      }
 222  
 223      // ----------------------------------
 224  
 225      /**
 226       * @param EditPage $editPage
 227       * @param string $action (edit/create/addurl...)
 228       * @return bool true if action triggers captcha on editPage's namespace
 229       */
 230  	function captchaTriggers( &$editPage, $action ) {
 231          global $wgCaptchaTriggers, $wgCaptchaTriggersOnNamespace;
 232          // Special config for this NS?
 233          if ( isset( $wgCaptchaTriggersOnNamespace[$editPage->mTitle->getNamespace()][$action] ) )
 234              return $wgCaptchaTriggersOnNamespace[$editPage->mTitle->getNamespace()][$action];
 235  
 236          return ( !empty( $wgCaptchaTriggers[$action] ) ); // Default
 237      }
 238  
 239      /**
 240       * @param $editPage EditPage
 241       * @param $newtext string
 242       * @param $section string
 243       * @param $merged bool
 244       * @return bool true if the captcha should run
 245       */
 246  	function shouldCheck( &$editPage, $newtext, $section, $merged = false ) {
 247          $this->trigger = '';
 248          $title = $editPage->mArticle->getTitle();
 249  
 250          global $wgUser;
 251          if ( $wgUser->isAllowed( 'skipcaptcha' ) ) {
 252              wfDebug( "ConfirmEdit: user group allows skipping captcha\n" );
 253              return false;
 254          }
 255          if ( $this->isIPWhitelisted() )
 256              return false;
 257  
 258  
 259          global $wgEmailAuthentication, $ceAllowConfirmedEmail;
 260          if ( $wgEmailAuthentication && $ceAllowConfirmedEmail &&
 261              $wgUser->isEmailConfirmed() ) {
 262              wfDebug( "ConfirmEdit: user has confirmed mail, skipping captcha\n" );
 263              return false;
 264          }
 265  
 266          if ( $this->captchaTriggers( $editPage, 'edit' ) ) {
 267              // Check on all edits
 268              global $wgUser;
 269              $this->trigger = sprintf( "edit trigger by '%s' at [[%s]]",
 270                  $wgUser->getName(),
 271                  $title->getPrefixedText() );
 272              $this->action = 'edit';
 273              wfDebug( "ConfirmEdit: checking all edits...\n" );
 274              return true;
 275          }
 276  
 277          if ( $this->captchaTriggers( $editPage, 'create' )  && !$editPage->mTitle->exists() ) {
 278              // Check if creating a page
 279              global $wgUser;
 280              $this->trigger = sprintf( "Create trigger by '%s' at [[%s]]",
 281                  $wgUser->getName(),
 282                  $title->getPrefixedText() );
 283              $this->action = 'create';
 284              wfDebug( "ConfirmEdit: checking on page creation...\n" );
 285              return true;
 286          }
 287  
 288          if ( $this->captchaTriggers( $editPage, 'addurl' ) ) {
 289              // Only check edits that add URLs
 290              if ( $merged ) {
 291                  // Get links from the database
 292                  $oldLinks = $this->getLinksFromTracker( $title );
 293                  // Share a parse operation with Article::doEdit()
 294                  $editInfo = $editPage->mArticle->prepareTextForEdit( $newtext );
 295                  $newLinks = array_keys( $editInfo->output->getExternalLinks() );
 296              } else {
 297                  // Get link changes in the slowest way known to man
 298                  $oldtext = $this->loadText( $editPage, $section );
 299                  $oldLinks = $this->findLinks( $editPage, $oldtext );
 300                  $newLinks = $this->findLinks( $editPage, $newtext );
 301              }
 302  
 303              $unknownLinks = array_filter( $newLinks, array( &$this, 'filterLink' ) );
 304              $addedLinks = array_diff( $unknownLinks, $oldLinks );
 305              $numLinks = count( $addedLinks );
 306  
 307              if ( $numLinks > 0 ) {
 308                  global $wgUser;
 309                  $this->trigger = sprintf( "%dx url trigger by '%s' at [[%s]]: %s",
 310                      $numLinks,
 311                      $wgUser->getName(),
 312                      $title->getPrefixedText(),
 313                      implode( ", ", $addedLinks ) );
 314                  $this->action = 'addurl';
 315                  return true;
 316              }
 317          }
 318  
 319          global $wgCaptchaRegexes;
 320          if ( $wgCaptchaRegexes ) {
 321              // Custom regex checks. Reuse $oldtext if set above.
 322              $oldtext = isset( $oldtext ) ? $oldtext : $this->loadText( $editPage, $section );
 323  
 324              foreach ( $wgCaptchaRegexes as $regex ) {
 325                  $newMatches = array();
 326                  if ( preg_match_all( $regex, $newtext, $newMatches ) ) {
 327                      $oldMatches = array();
 328                      preg_match_all( $regex, $oldtext, $oldMatches );
 329  
 330                      $addedMatches = array_diff( $newMatches[0], $oldMatches[0] );
 331  
 332                      $numHits = count( $addedMatches );
 333                      if ( $numHits > 0 ) {
 334                          global $wgUser;
 335                          $this->trigger = sprintf( "%dx %s at [[%s]]: %s",
 336                              $numHits,
 337                              $regex,
 338                              $wgUser->getName(),
 339                              $title->getPrefixedText(),
 340                              implode( ", ", $addedMatches ) );
 341                          $this->action = 'edit';
 342                          return true;
 343                      }
 344                  }
 345              }
 346          }
 347  
 348          return false;
 349      }
 350  
 351      /**
 352       * Filter callback function for URL whitelisting
 353       * @param $url string to check
 354       * @return bool true if unknown, false if whitelisted
 355       * @access private
 356       */
 357  	function filterLink( $url ) {
 358          global $wgCaptchaWhitelist;
 359          static $regexes = null;
 360  
 361          if ( $regexes === null ) {
 362              $source = wfMessage( 'captcha-addurl-whitelist' )->inContentLanguage();
 363  
 364              $regexes = $source->isDisabled()
 365                  ? array()
 366                  : $this->buildRegexes( explode( "\n", $source->plain() ) );
 367  
 368              if ( $wgCaptchaWhitelist !== false ) {
 369                  array_unshift( $regexes, $wgCaptchaWhitelist );
 370              }
 371          }
 372  
 373          foreach ( $regexes as $regex ) {
 374              if ( preg_match( $regex, $url ) ) {
 375                  return false;
 376              }
 377          }
 378  
 379          return true;
 380      }
 381  
 382      /**
 383       * Build regex from whitelist
 384       * @param $lines string from [[MediaWiki:Captcha-addurl-whitelist]]
 385       * @return array Regexes
 386       * @access private
 387       */
 388  	function buildRegexes( $lines ) {
 389          # Code duplicated from the SpamBlacklist extension (r19197)
 390          # and later modified.
 391  
 392          # Strip comments and whitespace, then remove blanks
 393          $lines = array_filter( array_map( 'trim', preg_replace( '/#.*$/', '', $lines ) ) );
 394  
 395          # No lines, don't make a regex which will match everything
 396          if ( count( $lines ) == 0 ) {
 397              wfDebug( "No lines\n" );
 398              return array();
 399          } else {
 400              # Make regex
 401              # It's faster using the S modifier even though it will usually only be run once
 402              // $regex = 'http://+[a-z0-9_\-.]*(' . implode( '|', $lines ) . ')';
 403              // return '/' . str_replace( '/', '\/', preg_replace('|\\\*/|', '/', $regex) ) . '/Si';
 404              $regexes = array();
 405              $regexStart = array(
 406                  'normal' => '/^https?:\/\/+[a-z0-9_\-.]*(?:',
 407                  'noprotocol' => '/^(?:',
 408              );
 409              $regexEnd = array(
 410                  'normal' => ')/Si',
 411                  'noprotocol' => ')/Si',
 412              );
 413              $regexMax = 4096;
 414              $build = array();
 415              foreach ( $lines as $line ) {
 416                  # Extract flags from the line
 417                  $options = array();
 418                  if ( preg_match( '/^(.*?)\s*<([^<>]*)>$/', $line, $matches ) ) {
 419                      if ( $matches[1] === '' ) {
 420                          wfDebug( "Line with empty regex\n" );
 421                          continue;
 422                      }
 423                      $line = $matches[1];
 424                      $opts = preg_split( '/\s*\|\s*/', trim( $matches[2] ) );
 425                      foreach ( $opts as $opt ) {
 426                          $opt = strtolower( $opt );
 427                          if ( $opt == 'noprotocol' ) {
 428                              $options['noprotocol'] = true;
 429                          }
 430                      }
 431                  }
 432  
 433                  $key = isset( $options['noprotocol'] ) ? 'noprotocol' : 'normal';
 434  
 435                  // FIXME: not very robust size check, but should work. :)
 436                  if ( !isset( $build[$key] ) ) {
 437                      $build[$key] = $line;
 438                  } elseif ( strlen( $build[$key] ) + strlen( $line ) > $regexMax ) {
 439                      $regexes[] = $regexStart[$key] .
 440                          str_replace( '/', '\/', preg_replace( '|\\\*/|', '/', $build[$key] ) ) .
 441                          $regexEnd[$key];
 442                      $build[$key] = $line;
 443                  } else {
 444                      $build[$key] .= '|' . $line;
 445                  }
 446              }
 447              foreach ( $build as $key => $value ) {
 448                  $regexes[] = $regexStart[$key] .
 449                      str_replace( '/', '\/', preg_replace( '|\\\*/|', '/', $build[$key] ) ) .
 450                      $regexEnd[$key];
 451              }
 452              return $regexes;
 453          }
 454      }
 455  
 456      /**
 457       * Load external links from the externallinks table
 458       * @param $title Title
 459       * @return Array
 460       */
 461  	function getLinksFromTracker( $title ) {
 462          $dbr = wfGetDB( DB_SLAVE );
 463          $id = $title->getArticleID(); // should be zero queries
 464          $res = $dbr->select( 'externallinks', array( 'el_to' ),
 465              array( 'el_from' => $id ), __METHOD__ );
 466          $links = array();
 467          foreach ( $res as $row ) {
 468              $links[] = $row->el_to;
 469          }
 470          return $links;
 471      }
 472  
 473      /**
 474       * Backend function for confirmEdit() and confirmEditAPI()
 475       * @param $editPage EditPage
 476       * @param $newtext string
 477       * @param $section
 478       * @param $merged bool
 479       * @return bool false if the CAPTCHA is rejected, true otherwise
 480       */
 481  	private function doConfirmEdit( $editPage, $newtext, $section, $merged = false ) {
 482          global $wgRequest;
 483          if ( $wgRequest->getVal( 'captchaid' ) ) {
 484              $wgRequest->setVal( 'wpCaptchaId', $wgRequest->getVal( 'captchaid' ) );
 485          }
 486          if ( $wgRequest->getVal( 'captchaword' ) ) {
 487              $wgRequest->setVal( 'wpCaptchaWord', $wgRequest->getVal( 'captchaword' ) );
 488          }
 489          if ( $this->shouldCheck( $editPage, $newtext, $section, $merged ) ) {
 490              return $this->passCaptcha();
 491          } else {
 492              wfDebug( "ConfirmEdit: no need to show captcha.\n" );
 493              return true;
 494          }
 495      }
 496  
 497      /**
 498       * The main callback run on edit attempts.
 499       * @param EditPage $editPage
 500       * @param string $newtext
 501       * @param string $section
 502       * @param bool $merged
 503       * @return bool true to continue saving, false to abort and show a captcha form
 504       */
 505  	function confirmEdit( $editPage, $newtext, $section, $merged = false ) {
 506          if ( defined( 'MW_API' ) ) {
 507              # API mode
 508              # The CAPTCHA was already checked and approved
 509              return true;
 510          }
 511          if ( !$this->doConfirmEdit( $editPage, $newtext, $section, $merged ) ) {
 512              $editPage->showEditForm( array( &$this, 'editCallback' ) );
 513              return false;
 514          }
 515          return true;
 516      }
 517  
 518      /**
 519       * A more efficient edit filter callback based on the text after section merging
 520       * @param EditPage $editPage
 521       * @param string $newtext
 522       * @return bool
 523       */
 524  	function confirmEditMerged( $editPage, $newtext ) {
 525          return $this->confirmEdit( $editPage, $newtext, false, true );
 526      }
 527  
 528  	function confirmEditAPI( $editPage, $newtext, &$resultArr ) {
 529          if ( !$this->doConfirmEdit( $editPage, $newtext, false, false ) ) {
 530              $this->addCaptchaAPI( $resultArr );
 531              return false;
 532          }
 533  
 534          return true;
 535      }
 536  
 537      /**
 538       * Hook for user creation form submissions.
 539       * @param User $u
 540       * @param string $message
 541       * @param Status $status
 542       * @return bool true to continue, false to abort user creation
 543       */
 544  	function confirmUserCreate( $u, &$message, &$status = null ) {
 545          if ( $this->needCreateAccountCaptcha() ) {
 546              $this->trigger = "new account '" . $u->getName() . "'";
 547              if ( !$this->passCaptcha() ) {
 548                  // For older MediaWiki
 549                  $message = wfMessage( 'captcha-createaccount-fail' )->text();
 550                  // For MediaWiki 1.23+
 551                  $status = Status::newGood();
 552                  
 553                  // Apply a *non*-fatal warning. This will still abort the
 554                  // account creation but returns a "Warning" response to the
 555                  // API or UI.
 556                  $status->warning( 'captcha-createaccount-fail' );
 557                  return false;
 558              }
 559          }
 560          return true;
 561      }
 562      
 563      /**
 564       * Logic to check if we need to pass a captcha for the current user
 565       * to create a new account, or not
 566       *
 567       * @return bool true to show captcha, false to skip captcha
 568       */
 569  	function needCreateAccountCaptcha() {
 570          global $wgCaptchaTriggers, $wgUser;
 571          if ( $wgCaptchaTriggers['createaccount'] ) {
 572              if ( $wgUser->isAllowed( 'skipcaptcha' ) ) {
 573                  wfDebug( "ConfirmEdit: user group allows skipping captcha on account creation\n" );
 574                  return false;
 575              }
 576              if ( $this->isIPWhitelisted() ) {
 577                  return false;
 578              }
 579              return true;
 580          }
 581          return false;
 582      }
 583  
 584      /**
 585       * Hook for user login form submissions.
 586       * @param $u User
 587       * @param $pass
 588       * @param $retval
 589       * @return bool true to continue, false to abort user creation
 590       */
 591  	function confirmUserLogin( $u, $pass, &$retval ) {
 592          if ( $this->isBadLoginTriggered() ) {
 593              if ( $this->isIPWhitelisted() )
 594                  return true;
 595  
 596              $this->trigger = "post-badlogin login '" . $u->getName() . "'";
 597              if ( !$this->passCaptcha() ) {
 598                  // Emulate a bad-password return to confuse the shit out of attackers
 599                  $retval = LoginForm::WRONG_PASS;
 600                  return false;
 601              }
 602          }
 603          return true;
 604      }
 605  
 606      /**
 607       * Check the captcha on Special:EmailUser
 608       * @param $from MailAddress
 609       * @param $to MailAddress
 610       * @param $subject String
 611       * @param $text String
 612       * @param $error String reference
 613       * @return Bool true to continue saving, false to abort and show a captcha form
 614       */
 615  	function confirmEmailUser( $from, $to, $subject, $text, &$error ) {
 616          global $wgCaptchaTriggers, $wgUser;
 617          if ( $wgCaptchaTriggers['sendemail'] ) {
 618              if ( $wgUser->isAllowed( 'skipcaptcha' ) ) {
 619                  wfDebug( "ConfirmEdit: user group allows skipping captcha on email sending\n" );
 620                  return true;
 621              }
 622              if ( $this->isIPWhitelisted() )
 623                  return true;
 624  
 625              if ( defined( 'MW_API' ) ) {
 626                  # API mode
 627                  # Asking for captchas in the API is really silly
 628                  $error = wfMessage( 'captcha-disabledinapi' )->text();
 629                  return false;
 630              }
 631              $this->trigger = "{$wgUser->getName()} sending email";
 632              if ( !$this->passCaptcha() ) {
 633                  $error = wfMessage( 'captcha-sendemail-fail' )->text();
 634                  return false;
 635              }
 636          }
 637          return true;
 638      }
 639  
 640      /**
 641       * @param $module ApiBase
 642       * @return bool
 643       */
 644  	protected function isAPICaptchaModule( $module ) {
 645          return $module instanceof ApiEditPage || $module instanceof ApiCreateAccount;
 646      }
 647  
 648      /**
 649       * @param $module ApiBase
 650       * @param $params array
 651       * @param $flags int
 652       * @return bool
 653       */
 654  	public function APIGetAllowedParams( &$module, &$params, $flags ) {
 655          if ( $flags && $this->isAPICaptchaModule( $module ) ) {
 656              $params['captchaword'] = null;
 657              $params['captchaid'] = null;
 658          }
 659  
 660          return true;
 661      }
 662  
 663      /**
 664       * @param $module ApiBase
 665       * @param $desc array
 666       * @return bool
 667       */
 668  	public function APIGetParamDescription( &$module, &$desc ) {
 669          if ( $this->isAPICaptchaModule( $module ) ) {
 670              $desc['captchaid'] = 'CAPTCHA ID from previous request';
 671              $desc['captchaword'] = 'Answer to the CAPTCHA';
 672          }
 673  
 674          return true;
 675      }
 676  
 677      /**
 678       * Given a required captcha run, test form input for correct
 679       * input on the open session.
 680       * @return bool if passed, false if failed or new session
 681       */
 682  	function passCaptcha() {
 683          $info = $this->retrieveCaptcha();
 684          if ( $info ) {
 685              global $wgRequest;
 686              if ( $this->keyMatch( $wgRequest->getVal( 'wpCaptchaWord' ), $info ) ) {
 687                  $this->log( "passed" );
 688                  $this->clearCaptcha( $info );
 689                  return true;
 690              } else {
 691                  $this->clearCaptcha( $info );
 692                  $this->log( "bad form input" );
 693                  return false;
 694              }
 695          } else {
 696              $this->log( "new captcha session" );
 697              return false;
 698          }
 699      }
 700  
 701      /**
 702       * Log the status and any triggering info for debugging or statistics
 703       * @param string $message
 704       */
 705  	function log( $message ) {
 706          wfDebugLog( 'captcha', 'ConfirmEdit: ' . $message . '; ' .  $this->trigger );
 707      }
 708  
 709      /**
 710       * Generate a captcha session ID and save the info in PHP's session storage.
 711       * (Requires the user to have cookies enabled to get through the captcha.)
 712       *
 713       * A random ID is used so legit users can make edits in multiple tabs or
 714       * windows without being unnecessarily hobbled by a serial order requirement.
 715       * Pass the returned id value into the edit form as wpCaptchaId.
 716       *
 717       * @param array $info data to store
 718       * @return string captcha ID key
 719       */
 720  	function storeCaptcha( $info ) {
 721          if ( !isset( $info['index'] ) ) {
 722              // Assign random index if we're not udpating
 723              $info['index'] = strval( mt_rand() );
 724          }
 725          CaptchaStore::get()->store( $info['index'], $info );
 726          return $info['index'];
 727      }
 728  
 729      /**
 730       * Fetch this session's captcha info.
 731       * @return mixed array of info, or false if missing
 732       */
 733  	function retrieveCaptcha() {
 734          global $wgRequest;
 735          $index = $wgRequest->getVal( 'wpCaptchaId' );
 736          return CaptchaStore::get()->retrieve( $index );
 737      }
 738  
 739      /**
 740       * Clear out existing captcha info from the session, to ensure
 741       * it can't be reused.
 742       */
 743  	function clearCaptcha( $info ) {
 744          CaptchaStore::get()->clear( $info['index'] );
 745      }
 746  
 747      /**
 748       * Retrieve the current version of the page or section being edited...
 749       * @param EditPage $editPage
 750       * @param string $section
 751       * @return string
 752       * @access private
 753       */
 754  	function loadText( $editPage, $section ) {
 755          $rev = Revision::newFromTitle( $editPage->mTitle, false, Revision::READ_LATEST );
 756          if ( is_null( $rev ) ) {
 757              return "";
 758          } else {
 759              $text = $rev->getText();
 760              if ( $section != '' ) {
 761                  global $wgParser;
 762                  return $wgParser->getSection( $text, $section );
 763              } else {
 764                  return $text;
 765              }
 766          }
 767      }
 768  
 769      /**
 770       * Extract a list of all recognized HTTP links in the text.
 771       * @param $editpage EditPage
 772       * @param $text string
 773       * @return array of strings
 774       */
 775  	function findLinks( &$editpage, $text ) {
 776          global $wgParser, $wgUser;
 777  
 778          $options = new ParserOptions();
 779          $text = $wgParser->preSaveTransform( $text, $editpage->mTitle, $wgUser, $options );
 780          $out = $wgParser->parse( $text, $editpage->mTitle, $options );
 781  
 782          return array_keys( $out->getExternalLinks() );
 783      }
 784  
 785      /**
 786       * Show a page explaining what this wacky thing is.
 787       */
 788  	function showHelp() {
 789          global $wgOut;
 790          $wgOut->setPageTitle( wfMessage( 'captchahelp-title' )->text() );
 791          $wgOut->addWikiMsg( 'captchahelp-text' );
 792          if ( CaptchaStore::get()->cookiesNeeded() ) {
 793              $wgOut->addWikiMsg( 'captchahelp-cookies-needed' );
 794          }
 795      }
 796  
 797      /**
 798       * Pass API captcha parameters on to the login form when using
 799       * API account creation.
 800       *
 801       * @param ApiCreateAccount $apiModule
 802       * @param LoginForm $loginForm
 803       * @return hook return value
 804       */
 805  	function addNewAccountApiForm( $apiModule, $loginForm ) {
 806          global $wgRequest;
 807          $main = $apiModule->getMain();
 808  
 809          $id = $main->getVal( 'captchaid' );
 810          if ( $id ) {
 811              $wgRequest->setVal( 'wpCaptchaId', $id );
 812  
 813              // Suppress "unrecognized parameter" warning:
 814              $main->getVal( 'wpCaptchaId' );
 815          }
 816  
 817          $word = $main->getVal( 'captchaword' );
 818          if ( $word ) {
 819              $wgRequest->setVal( 'wpCaptchaWord', $word );
 820  
 821              // Suppress "unrecognized parameter" warning:
 822              $main->getVal( 'wpCaptchaWord' );
 823          }
 824  
 825          return true;
 826      }
 827  
 828      /**
 829       * Pass extra data back in API results for account creation.
 830       *
 831       * @param ApiCreateAccount $apiModule
 832       * @param LoginForm &loginPage
 833       * @param array &$result
 834       * @return bool: Hook return value
 835       */
 836  	function addNewAccountApiResult( $apiModule, $loginPage, &$result ) {
 837          if ( $result['result'] !== 'Success' && $this->needCreateAccountCaptcha() ) {
 838  
 839              // If we failed a captcha, override the generic 'Warning' result string
 840              if ( $result['result'] === 'Warning' && isset( $result['warnings'] ) ) {
 841                  foreach ( $result['warnings'] as $warning ) {
 842                      if ( $warning['message'] === 'captcha-createaccount-fail' ) {
 843                          $this->addCaptchaAPI( $result );
 844                          $result['result'] = 'NeedCaptcha';
 845                      }
 846                  }
 847              }
 848          }
 849          return true;
 850      }
 851  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1