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