]> ToastFreeware Gitweb - toast/cookiecaptcha.git/blob - ConfirmEdit.php
committing progress so i don't forget to save changes this month
[toast/cookiecaptcha.git] / ConfirmEdit.php
1 <?php
2
3 # Prelim in-progress code. Proof of concept for framework, not
4 # intended as a real production captcha system!
5
6 # Loader for simple captcha feature
7 # Include this from LocalSettings.php
8
9 if ( defined( 'MEDIAWIKI' ) ) {
10
11 global $wgExtensionFunctions, $wgGroupPermissions;
12
13 $wgExtensionFunctions[] = 'ceSetup';
14
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;
20
21 global $wgCaptcha, $wgCaptchaClass, $wgCaptchaTriggers;
22 $wgCaptcha = null;
23 $wgCaptchaClass = 'SimpleCaptcha';
24
25 $wgCaptchaTriggers = array();
26 $wgCaptchaTriggers['edit']   = false; // Would check on every edit
27 $wgCaptchaTriggers['addurl'] = true;  // Check on edits that add URLs
28
29 /**
30  * Allow users who have confirmed their e-mail addresses to post
31  * URL links without being harassed by the captcha.
32  */
33 global $ceAllowConfirmedEmail;
34 $ceAllowConfirmedEmail = false;
35
36 /**
37  * Set up message strings for captcha utilities.
38  */
39 function ceSetup() {
40         global $wgMessageCache, $wgHooks, $wgCaptcha, $wgCaptchaClass;
41         $wgMessageCache->addMessages( array(
42                 'captcha-short' =>
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' =>
47                         'Captcha help',
48                 'captchahelp-text' =>
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." .
53                         "\n\n" .
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." .
59                         "\n\n" .
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." . 
65                         "\n\n" .
66                         "Hit the 'back' button in your browser to return to the page editor." ) );
67         
68         SpecialPage::addPage( new SpecialPage( 'Captcha', false,
69                 /*listed*/ false, /*function*/ false, /*file*/ false ) );
70         
71         $wgCaptcha = new $wgCaptchaClass();
72         $wgHooks['EditFilter'][] = array( &$wgCaptcha, 'confirmEdit' );
73 }
74
75 /**
76  * Entry point for Special:Captcha
77  */
78 function wfSpecialCaptcha( $par = null ) {
79         global $wgCaptcha;
80         switch( $par ) {
81         case "image":
82                 return $wgCaptcha->showImage();
83         case "help":
84         default:
85                 return $wgCaptcha->showHelp();
86         }
87 }
88
89 class SimpleCaptcha {
90         /**
91          * @param EditPage $editPage
92          * @param string $newtext
93          * @param string $section
94          * @return bool true if the captcha should run
95          */
96         function shouldCheck( &$editPage, $newtext, $section ) {
97                 global $wgUser;
98                 if( $wgUser->isAllowed( 'skipcaptcha' ) ) {
99                         wfDebug( "SimpleCaptcha: user group allows skipping captcha\n" );
100                         return false;
101                 }
102         
103                 global $wgEmailAuthentication, $ceAllowConfirmedEmail;
104                 if( $wgEmailAuthentication && $ceAllowConfirmedEmail &&
105                         $wgUser->isEmailConfirmed() ) {
106                         wfDebug( "SimpleCaptcha: user has confirmed mail, skipping captcha\n" );
107                         return false;
108                 }
109                 
110                 global $wgCaptchaTriggers;
111                 if( !empty( $wgCaptchaTriggers['edit'] ) ) {
112                         // Check on all edits
113                         wfDebug( "SimpleCaptcha: checking all edits...\n" );
114                         return true;
115                 }
116                 
117                 if( !empty( $wgCaptchaTriggers['addurl'] ) ) {
118                         // Only check edits that add URLs
119                         $oldtext = $this->loadText( $editPage, $section );
120                         
121                         $oldLinks = $this->findLinks( $oldtext );
122                         $newLinks = $this->findLinks( $newtext );
123                         
124                         $addedLinks = array_diff( $newLinks, $oldLinks );
125                         $numLinks = count( $addedLinks );
126                         
127                         if( $numLinks > 0 ) {
128                                 global $wgUser, $wgTitle;
129                                 wfDebugLog( "captcha", sprintf( "ConfirmEdit: %dx url trigger by %s at [[%s]]: %s",
130                                         $numLinks,
131                                         $wgUser->getName(),
132                                         $wgTitle->getPrefixedText(),
133                                         implode( ", ", $addedLinks ) ) );
134                                 return true;
135                         }
136                 }
137                 
138                 return false;
139         }
140         
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" );
145                                 return true;
146                         } else {
147                                 wfDebug( "ConfirmEdit missing form key, prompting.\n" );
148                                 $editPage->showEditForm( array( &$this, 'formCallback' ) );
149                                 return false;
150                         }
151                 } else {
152                         wfDebug( "ConfirmEdit: no new links.\n" );
153                         return true;
154                 }
155         }
156         
157         function keyMatch() {
158                 if( !isset( $_SESSION['ceAnswerVar'] ) ) {
159                         wfDebug( "ConfirmEdit no session captcha key set, this is new visitor.\n" );
160                         return false;
161                 }
162                 global $wgRequest;
163                 return $wgRequest->getVal( $_SESSION['ceAnswerVar'] ) == $_SESSION['ceAnswer'];
164         }
165         
166         function formCallback( &$out ) {
167                 $source = 'ceSource' . mt_rand();
168                 $dest = 'ceConfirm' . mt_rand();
169                 
170                 $a = mt_rand(0, 100);
171                 $b = mt_rand(0, 10);
172                 $op = mt_rand(0, 1) ? '+' : '-';
173                 
174                 $test = "$a $op $b";
175                 $answer = ($op == '+') ? ($a + $b) : ($a - $b);
176                 $_SESSION['ceAnswer'] = $answer;
177                 $_SESSION['ceAnswerVar'] = $dest;
178                 
179                 
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>
183 END
184                         );
185         }
186         
187         function loadText( $editPage, $section ) {
188                 $rev = Revision::newFromTitle( $editPage->mTitle );
189                 if( is_null( $rev ) ) {
190                         return "";
191                 } else {
192                         $text = $rev->getText();
193                         if( $section != '' ) {
194                                 return Article::getSection( $text, $section );
195                         } else {
196                                 return $text;
197                         }
198                 }
199         }
200         
201         function findLinks( $text ) {
202                 $regex = '/((?:' . HTTP_PROTOCOLS . ')' . EXT_LINK_URL_CLASS . '+)/';
203                 
204                 if( preg_match_all( $regex, $text, $matches, PREG_PATTERN_ORDER ) ) {
205                         return $matches[1];
206                 } else {
207                         return array();
208                 }
209         }
210         
211         function showHelp() {
212                 global $wgOut, $ceAllowConfirmedEmail;
213                 $wgOut->setPageTitle( wfMsg( 'captchahelp-title' ) );
214                 $wgOut->addWikiText( wfMsg( 'captchahelp-text' ) );
215         }
216         
217 }
218
219 } # End invocation guard
220
221 ?>