[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |