3 # Prelim in-progress code. Proof of concept for framework, not
4 # intended as a real production captcha system!
6 # Loader for simple captcha feature
7 # Include this from LocalSettings.php
9 if ( defined( 'MEDIAWIKI' ) ) {
11 global $wgExtensionFunctions, $wgGroupPermissions;
13 $wgExtensionFunctions[] = 'ceSetup';
15 $wgGroupPermissions['*' ]['skipcaptcha'] = false;
16 $wgGroupPermissions['user' ]['skipcaptcha'] = false;
17 $wgGroupPermissions['autoconfirmed']['skipcaptcha'] = false;
18 $wgGroupPermissions['bot' ]['skipcaptcha'] = true; // registered bots
19 $wgGroupPermissions['sysop' ]['skipcaptcha'] = true;
21 global $wgCaptcha, $wgCaptchaClass, $wgCaptchaTriggers;
23 $wgCaptchaClass = 'SimpleCaptcha';
25 $wgCaptchaTriggers = array();
26 $wgCaptchaTriggers['edit'] = false; // Would check on every edit
27 $wgCaptchaTriggers['addurl'] = true; // Check on edits that add URLs
30 * Allow users who have confirmed their e-mail addresses to post
31 * URL links without being harassed by the captcha.
33 global $ceAllowConfirmedEmail;
34 $ceAllowConfirmedEmail = false;
37 * Set up message strings for captcha utilities.
40 global $wgMessageCache, $wgHooks, $wgCaptcha, $wgCaptchaClass;
41 $wgMessageCache->addMessages( array(
43 "Your edit includes new URL links; as a protection against automated " .
44 "spam, you'll need to type in the words that appear in this image:\n" .
45 "<br />([[Special:Captcha/help|What is this?]])",
46 'captchahelp-title' =>
49 "Web sites that accept postings from the public, like this wiki, " .
50 "are often abused by spammers who use automated tools to post their " .
51 "links to many sites. While these spam links can be removed, they " .
52 "are a significant nuisance." .
54 "Sometimes, especially when adding new web links to a page, " .
55 "the wiki may show you an image of colored or distorted text and " .
56 "ask you to type the words shown. Since this is a task that's hard " .
57 "to automate, it will allow most real humans to make their posts " .
58 "while stopping most spammers and other robotic attackers." .
60 "Unfortunately this may inconvenience users with limited vision or " .
61 "using text-based or speech-based browsers. At the moment we do not " .
62 "have an audio alternative available. Please contact the site " .
63 "administrators for assistance if this is unexpectedly preventing " .
64 "you from making legitimate posts." .
66 "Hit the 'back' button in your browser to return to the page editor." ) );
68 SpecialPage::addPage( new SpecialPage( 'Captcha', false,
69 /*listed*/ false, /*function*/ false, /*file*/ false ) );
71 $wgCaptcha = new $wgCaptchaClass();
72 $wgHooks['EditFilter'][] = array( &$wgCaptcha, 'confirmEdit' );
76 * Entry point for Special:Captcha
78 function wfSpecialCaptcha( $par = null ) {
82 return $wgCaptcha->showImage();
85 return $wgCaptcha->showHelp();
91 * @param EditPage $editPage
92 * @param string $newtext
93 * @param string $section
94 * @return bool true if the captcha should run
96 function shouldCheck( &$editPage, $newtext, $section ) {
98 if( $wgUser->isAllowed( 'skipcaptcha' ) ) {
99 wfDebug( "SimpleCaptcha: user group allows skipping captcha\n" );
103 global $wgEmailAuthentication, $ceAllowConfirmedEmail;
104 if( $wgEmailAuthentication && $ceAllowConfirmedEmail &&
105 $wgUser->isEmailConfirmed() ) {
106 wfDebug( "SimpleCaptcha: user has confirmed mail, skipping captcha\n" );
110 global $wgCaptchaTriggers;
111 if( !empty( $wgCaptchaTriggers['edit'] ) ) {
112 // Check on all edits
113 wfDebug( "SimpleCaptcha: checking all edits...\n" );
117 if( !empty( $wgCaptchaTriggers['addurl'] ) ) {
118 // Only check edits that add URLs
119 $oldtext = $this->loadText( $editPage, $section );
121 $oldLinks = $this->findLinks( $oldtext );
122 $newLinks = $this->findLinks( $newtext );
124 $addedLinks = array_diff( $newLinks, $oldLinks );
125 $numLinks = count( $addedLinks );
127 if( $numLinks > 0 ) {
128 global $wgUser, $wgTitle;
129 wfDebugLog( "captcha", sprintf( "ConfirmEdit: %dx url trigger by %s at [[%s]]: %s",
132 $wgTitle->getPrefixedText(),
133 implode( ", ", $addedLinks ) ) );
141 function confirmEdit( &$editPage, $newtext, $section ) {
142 if( $this->shouldCheck( $editPage, $newtext, $section ) ) {
143 if( $this->keyMatch() ) {
144 wfDebug( "ConfirmEdit given proper key from form, passing.\n" );
147 wfDebug( "ConfirmEdit missing form key, prompting.\n" );
148 $editPage->showEditForm( array( &$this, 'formCallback' ) );
152 wfDebug( "ConfirmEdit: no new links.\n" );
157 function keyMatch() {
158 if( !isset( $_SESSION['ceAnswerVar'] ) ) {
159 wfDebug( "ConfirmEdit no session captcha key set, this is new visitor.\n" );
163 return $wgRequest->getVal( $_SESSION['ceAnswerVar'] ) == $_SESSION['ceAnswer'];
166 function formCallback( &$out ) {
167 $source = 'ceSource' . mt_rand();
168 $dest = 'ceConfirm' . mt_rand();
170 $a = mt_rand(0, 100);
172 $op = mt_rand(0, 1) ? '+' : '-';
175 $answer = ($op == '+') ? ($a + $b) : ($a - $b);
176 $_SESSION['ceAnswer'] = $answer;
177 $_SESSION['ceAnswerVar'] = $dest;
180 $out->addWikiText( wfMsg( "captcha-short" ) );
181 $out->addHTML( <<<END
182 <p><span id="$source"><label for="$dest">$test</label></span> = <input name="$dest" id="$dest" /></p>
187 function loadText( $editPage, $section ) {
188 $rev = Revision::newFromTitle( $editPage->mTitle );
189 if( is_null( $rev ) ) {
192 $text = $rev->getText();
193 if( $section != '' ) {
194 return Article::getSection( $text, $section );
201 function findLinks( $text ) {
202 $regex = '/((?:' . HTTP_PROTOCOLS . ')' . EXT_LINK_URL_CLASS . '+)/';
204 if( preg_match_all( $regex, $text, $matches, PREG_PATTERN_ORDER ) ) {
211 function showHelp() {
212 global $wgOut, $ceAllowConfirmedEmail;
213 $wgOut->setPageTitle( wfMsg( 'captchahelp-title' ) );
214 $wgOut->addWikiText( wfMsg( 'captchahelp-text' ) );
219 } # End invocation guard