* Adding a <label> associated with the <input>
[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['bot'      ]['skipcaptcha'] = true; // registered bots
18 $wgGroupPermissions['sysop'    ]['skipcaptcha'] = true;
19
20 global $wgCaptcha, $wgCaptchaClass, $wgCaptchaTriggers;
21 $wgCaptcha = null;
22 $wgCaptchaClass = 'SimpleCaptcha';
23
24 $wgCaptchaTriggers = array();
25 $wgCaptchaTriggers['edit']   = false; // Would check on every edit
26 $wgCaptchaTriggers['addurl'] = true;  // Check on edits that add URLs
27
28 /**
29  * Allow users who have confirmed their e-mail addresses to post
30  * URL links without being harassed by the captcha.
31  */
32 global $ceAllowConfirmedEmail;
33 $ceAllowConfirmedEmail = false;
34
35 /**
36  * Set up message strings for captcha utilities.
37  */
38 function ceSetup() {
39         global $wgMessageCache, $wgHooks, $wgCaptcha, $wgCaptchaClass;
40         $wgMessageCache->addMessage('captcha-short', "Your edit includes new URL links; as a protection
41                 against automated spam, you'll need to enter the answer to this
42                 simple arithmetic test:" );
43         
44         SpecialPage::addPage( new SpecialPage( 'Captcha', false,
45                 /*listed*/ false, /*function*/ false, /*file*/ false ) );
46         
47         $wgCaptcha = new $wgCaptchaClass();
48         $wgHooks['EditFilter'][] = array( &$wgCaptcha, 'confirmEdit' );
49 }
50
51 /**
52  * Entry point for Special:Captcha
53  */
54 function wfSpecialCaptcha( $par = null ) {
55         global $wgCaptcha;
56         switch( $par ) {
57         case "image":
58                 return $wgCaptcha->showImage();
59         case "help":
60         default:
61                 return $wgCaptcha->showHelp();
62         }
63 }
64
65 class SimpleCaptcha {
66         /**
67          * @param EditPage $editPage
68          * @param string $newtext
69          * @param string $section
70          * @return bool true if the captcha should run
71          */
72         function shouldCheck( &$editPage, $newtext, $section ) {
73                 global $wgUser;
74                 if( $wgUser->isAllowed( 'skipcaptcha' ) ) {
75                         wfDebug( "SimpleCaptcha: user group allows skipping captcha\n" );
76                         return false;
77                 }
78         
79                 global $wgEmailAuthentication, $ceAllowConfirmedEmail;
80                 if( $wgEmailAuthentication && $ceAllowConfirmedEmail &&
81                         $wgUser->isEmailConfirmed() ) {
82                         wfDebug( "SimpleCaptcha: user has confirmed mail, skipping captcha\n" );
83                         return false;
84                 }
85                 
86                 global $wgCaptchaTriggers;
87                 if( !empty( $wgCaptchaTriggers['edit'] ) ) {
88                         // Check on all edits
89                         wfDebug( "SimpleCaptcha: checking all edits...\n" );
90                         return true;
91                 }
92                 
93                 if( !empty( $wgCaptchaTriggers['addurl'] ) ) {
94                         // Only check edits that add URLs
95                         $oldtext = $this->loadText( $editPage, $section );
96                         
97                         $oldLinks = $this->findLinks( $oldtext );
98                         $newLinks = $this->findLinks( $newtext );
99                         
100                         $addedLinks = array_diff( $newLinks, $oldLinks );
101                         $numLinks = count( $addedLinks );
102                         
103                         if( $numLinks > 0 ) {
104                                 wfDebug( "SimpleCaptcha: found $numLinks new links; triggered...\n" );
105                                 return true;
106                         }
107                 }
108                 
109                 return false;
110         }
111         
112         function confirmEdit( &$editPage, $newtext, $section ) {
113                 if( $this->shouldCheck( $editPage, $newtext, $section ) ) {
114                         if( $this->keyMatch() ) {
115                                 wfDebug( "ConfirmEdit given proper key from form, passing.\n" );
116                                 return true;
117                         } else {
118                                 wfDebug( "ConfirmEdit missing form key, prompting.\n" );
119                                 $editPage->showEditForm( array( &$this, 'formCallback' ) );
120                                 return false;
121                         }
122                 } else {
123                         wfDebug( "ConfirmEdit: no new links.\n" );
124                         return true;
125                 }
126         }
127         
128         function keyMatch() {
129                 if( !isset( $_SESSION['ceAnswerVar'] ) ) {
130                         wfDebug( "ConfirmEdit no session captcha key set, this is new visitor.\n" );
131                         return false;
132                 }
133                 global $wgRequest;
134                 return $wgRequest->getVal( $_SESSION['ceAnswerVar'] ) == $_SESSION['ceAnswer'];
135         }
136         
137         function formCallback( &$out ) {
138                 $source = 'ceSource' . mt_rand();
139                 $dest = 'ceConfirm' . mt_rand();
140                 
141                 $a = mt_rand(0, 100);
142                 $b = mt_rand(0, 10);
143                 $op = mt_rand(0, 1) ? '+' : '-';
144                 
145                 $test = "$a $op $b";
146                 $answer = ($op == '+') ? ($a + $b) : ($a - $b);
147                 $_SESSION['ceAnswer'] = $answer;
148                 $_SESSION['ceAnswerVar'] = $dest;
149                 
150                 
151                 $out->addWikiText( wfMsg( "captcha-short" ) );  
152                 $out->addHTML( <<<END
153                         <p><span id="$source"><label for="$dest">$test</label></span> = <input name="$dest" id="$dest" /></p>
154 END
155                         );
156         }
157         
158         function loadText( $editPage, $section ) {
159                 $rev = Revision::newFromTitle( $editPage->mTitle );
160                 if( is_null( $rev ) ) {
161                         return "";
162                 } else {
163                         $text = $rev->getText();
164                         if( $section != '' ) {
165                                 return Article::getSection( $text, $section );
166                         } else {
167                                 return $text;
168                         }
169                 }
170         }
171         
172         function findLinks( $text ) {
173                 $regex = '/((?:' . HTTP_PROTOCOLS . ')' . EXT_LINK_URL_CLASS . '+)/';
174                 
175                 if( preg_match_all( $regex, $text, $matches, PREG_PATTERN_ORDER ) ) {
176                         return $matches[1];
177                 } else {
178                         return array();
179                 }
180         }
181         
182         function showHelp() {
183                 global $wgOut, $ceAllowConfirmedEmail;
184                 $wgOut->setPageTitle( 'Captcha help' );
185                 $wgOut->addWikiText( <<<END
186         So what's this wacky captcha thing about?
187         
188         It's your enemy. It's here to kill you. RUN WHILE YOU STILL CAN
189 END
190                         );
191         }
192         
193 }
194
195 } # End invocation guard
196
197 ?>