~ubuntu-branches/ubuntu/saucy/mediawiki-extensions/saucy

« back to all changes in this revision

Viewing changes to dist/mediawiki-extensions-confirmedit/usr/share/mediawiki-extensions/confirmedit/ConfirmEdit_body.php

  • Committer: Bazaar Package Importer
  • Author(s): Romain Beauxis
  • Date: 2010-05-04 15:13:35 UTC
  • mfrom: (0.1.1 experimental)
  • Revision ID: james.westby@ubuntu.com-20100504151335-54qeucg3ec108q28
Tags: 2.2
* Added Replaces:/Conflicts: to allow a proper upgrade.
Closes: #580066
* Fixed package descriptions.
Closes: #579667
* Patched mediawiki-extensions-fckeditor to make it work with
  php 5.3. The fix may not be perfect but at least it work.
  Not closing the bug (#579822) for now..

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
 
 
3
class ConfirmEditHooks {
 
4
        static function getInstance() {
 
5
                global $wgCaptcha, $wgCaptchaClass, $wgExtensionMessagesFiles;
 
6
                static $done = false;
 
7
                if ( !$done ) {
 
8
                        $done = true;
 
9
                        wfLoadExtensionMessages( 'ConfirmEdit' );
 
10
                        if ( isset( $wgExtensionMessagesFiles[$wgCaptchaClass] ) ) {
 
11
                                wfLoadExtensionMessages( $wgCaptchaClass );
 
12
                        }
 
13
                        $wgCaptcha = new $wgCaptchaClass;
 
14
                }
 
15
                return $wgCaptcha;
 
16
        }
 
17
 
 
18
        static function confirmEdit( &$editPage, $newtext, $section ) {
 
19
                return self::getInstance()->confirmEdit( $editPage, $newtext, $section );
 
20
        }
 
21
 
 
22
        static function confirmEditMerged( &$editPage, $newtext ) {
 
23
                return self::getInstance()->confirmEditMerged( $editPage, $newtext );
 
24
        }
 
25
        
 
26
        static function confirmEditAPI( &$editPage, $newtext, &$resultArr ) {
 
27
                return self::getInstance()->confirmEditAPI( $editPage, $newtext, $resultArr );
 
28
        }
 
29
 
 
30
        static function injectUserCreate( &$template ) {
 
31
                return self::getInstance()->injectUserCreate( $template );
 
32
        }
 
33
 
 
34
        static function confirmUserCreate( $u, &$message ) {
 
35
                return self::getInstance()->confirmUserCreate( $u, $message );
 
36
        }
 
37
 
 
38
        static function triggerUserLogin( $user, $password, $retval ) {
 
39
                return self::getInstance()->triggerUserLogin( $user, $password, $retval );
 
40
        }
 
41
 
 
42
        static function injectUserLogin( &$template ) {
 
43
                return self::getInstance()->injectUserLogin( $template );
 
44
        }
 
45
 
 
46
        static function confirmUserLogin( $u, $pass, &$retval ) {
 
47
                return self::getInstance()->confirmUserLogin( $u, $pass, $retval );
 
48
        }
 
49
}
 
50
 
 
51
class CaptchaSpecialPage extends UnlistedSpecialPage {
 
52
        function execute( $par ) {
 
53
                $this->setHeaders();
 
54
                $instance = ConfirmEditHooks::getInstance();
 
55
                switch( $par ) {
 
56
                case "image":
 
57
                        if( method_exists($instance,'showImage') )
 
58
                                return $instance->showImage();
 
59
                case "help":
 
60
                default:
 
61
                        return $instance->showHelp();
 
62
                }
 
63
        }
 
64
}
 
65
 
 
66
 
 
67
class SimpleCaptcha {
 
68
        function SimpleCaptcha() {
 
69
                global $wgCaptchaStorageClass;
 
70
                $this->storage = new $wgCaptchaStorageClass;
 
71
        }
 
72
        
 
73
        function getCaptcha() {
 
74
                $a = mt_rand(0, 100);
 
75
                $b = mt_rand(0, 10);
 
76
                $op = mt_rand(0, 1) ? '+' : '-';
 
77
 
 
78
                $test = "$a $op $b";
 
79
                $answer = ($op == '+') ? ($a + $b) : ($a - $b);
 
80
                return array('question' => $test, 'answer' => $answer);
 
81
        }
 
82
        
 
83
        function addCaptchaAPI(&$resultArr) {
 
84
                $captcha = $this->getCaptcha();
 
85
                $index = $this->storeCaptcha( $captcha );
 
86
                $resultArr['captcha']['type'] = 'simple';
 
87
                $resultArr['captcha']['mime'] = 'text/plain';
 
88
                $resultArr['captcha']['id'] = $index;
 
89
                $resultArr['captcha']['question'] = $captcha['question'];
 
90
        }
 
91
        
 
92
        /**
 
93
         * Insert a captcha prompt into the edit form.
 
94
         * This sample implementation generates a simple arithmetic operation;
 
95
         * it would be easy to defeat by machine.
 
96
         *
 
97
         * Override this!
 
98
         *
 
99
         * @return string HTML
 
100
         */
 
101
        function getForm() {
 
102
                $captcha = $this->getCaptcha();
 
103
                $index = $this->storeCaptcha( $captcha );
 
104
 
 
105
                return "<p><label for=\"wpCaptchaWord\">{$captcha['question']}</label> = " .
 
106
                        Xml::element( 'input', array(
 
107
                                'name' => 'wpCaptchaWord',
 
108
                                'id'   => 'wpCaptchaWord',
 
109
                                'tabindex' => 1 ) ) . // tab in before the edit textarea
 
110
                        "</p>\n" .
 
111
                        Xml::element( 'input', array(
 
112
                                'type'  => 'hidden',
 
113
                                'name'  => 'wpCaptchaId',
 
114
                                'id'    => 'wpCaptchaId',
 
115
                                'value' => $index ) );
 
116
        }
 
117
 
 
118
        /**
 
119
         * Insert the captcha prompt into an edit form.
 
120
         * @param OutputPage $out
 
121
         */
 
122
        function editCallback( &$out ) {
 
123
                $out->addWikiText( $this->getMessage( $this->action ) );
 
124
                $out->addHTML( $this->getForm() );
 
125
        }
 
126
 
 
127
        /**
 
128
         * Show a message asking the user to enter a captcha on edit
 
129
         * The result will be treated as wiki text
 
130
         *
 
131
         * @param $action Action being performed
 
132
         * @return string
 
133
         */
 
134
        function getMessage( $action ) {
 
135
                $name = 'captcha-' . $action;
 
136
                $text = wfMsg( $name );
 
137
                # Obtain a more tailored message, if possible, otherwise, fall back to
 
138
                # the default for edits
 
139
                return wfEmptyMsg( $name, $text ) ? wfMsg( 'captcha-edit' ) : $text;
 
140
        }
 
141
 
 
142
        /**
 
143
         * Inject whazawhoo
 
144
         * @fixme if multiple thingies insert a header, could break
 
145
         * @param SimpleTemplate $template
 
146
         * @return bool true to keep running callbacks
 
147
         */
 
148
        function injectUserCreate( &$template ) {
 
149
                global $wgCaptchaTriggers, $wgOut, $wgUser;
 
150
                if( $wgCaptchaTriggers['createaccount'] ) {
 
151
                        if( $wgUser->isAllowed( 'skipcaptcha' ) ) {
 
152
                                wfDebug( "ConfirmEdit: user group allows skipping captcha on account creation\n" );
 
153
                                return true;
 
154
                        }
 
155
                        $template->set( 'header',
 
156
                                "<div class='captcha'>" .
 
157
                                $wgOut->parse( $this->getMessage( 'createaccount' ) ) .
 
158
                                $this->getForm() .
 
159
                                "</div>\n" );
 
160
                }
 
161
                return true;
 
162
        }
 
163
 
 
164
        /**
 
165
         * Inject a captcha into the user login form after a failed
 
166
         * password attempt as a speedbump for mass attacks.
 
167
         * @fixme if multiple thingies insert a header, could break
 
168
         * @param SimpleTemplate $template
 
169
         * @return bool true to keep running callbacks
 
170
         */
 
171
        function injectUserLogin( &$template ) {
 
172
                if( $this->isBadLoginTriggered() ) {
 
173
                        global $wgOut;
 
174
                        $template->set( 'header',
 
175
                                "<div class='captcha'>" .
 
176
                                $wgOut->parse( $this->getMessage( 'badlogin' ) ) .
 
177
                                $this->getForm() .
 
178
                                "</div>\n" );
 
179
                }
 
180
                return true;
 
181
        }
 
182
        
 
183
        /**
 
184
         * When a bad login attempt is made, increment an expiring counter
 
185
         * in the memcache cloud. Later checks for this may trigger a
 
186
         * captcha display to prevent too many hits from the same place.
 
187
         * @param User $user
 
188
         * @param string $password
 
189
         * @param int $retval authentication return value
 
190
         * @return bool true to keep running callbacks
 
191
         */
 
192
        function triggerUserLogin( $user, $password, $retval ) {
 
193
                global $wgCaptchaTriggers, $wgCaptchaBadLoginExpiration, $wgMemc;
 
194
                if( $retval == LoginForm::WRONG_PASS && $wgCaptchaTriggers['badlogin'] ) {
 
195
                        $key = $this->badLoginKey();
 
196
                        $count = $wgMemc->get( $key );
 
197
                        if( !$count ) {
 
198
                                $wgMemc->add( $key, 0, $wgCaptchaBadLoginExpiration );
 
199
                        }
 
200
                        $count = $wgMemc->incr( $key );
 
201
                }
 
202
                return true;
 
203
        }
 
204
        
 
205
        /**
 
206
         * Check if a bad login has already been registered for this
 
207
         * IP address. If so, require a captcha.
 
208
         * @return bool
 
209
         * @access private
 
210
         */
 
211
        function isBadLoginTriggered() {
 
212
                global $wgMemc, $wgCaptchaBadLoginAttempts;
 
213
                return intval( $wgMemc->get( $this->badLoginKey() ) ) >= $wgCaptchaBadLoginAttempts;
 
214
        }
 
215
        
 
216
        /**
 
217
         * Internal cache key for badlogin checks.
 
218
         * @return string
 
219
         * @access private
 
220
         */
 
221
        function badLoginKey() {
 
222
                return wfMemcKey( 'captcha', 'badlogin', 'ip', wfGetIP() );
 
223
        }
 
224
        
 
225
        /**
 
226
         * Check if the submitted form matches the captcha session data provided
 
227
         * by the plugin when the form was generated.
 
228
         *
 
229
         * Override this!
 
230
         *
 
231
         * @param string $answer
 
232
         * @param array $info
 
233
         * @return bool
 
234
         */
 
235
        function keyMatch( $answer, $info ) {
 
236
                return $answer == $info['answer'];
 
237
        }
 
238
 
 
239
        // ----------------------------------
 
240
 
 
241
        /**
 
242
         * @param EditPage $editPage
 
243
         * @param string $action (edit/create/addurl...)
 
244
         * @return bool true if action triggers captcha on editPage's namespace
 
245
         */
 
246
        function captchaTriggers( &$editPage, $action) {
 
247
                global $wgCaptchaTriggers, $wgCaptchaTriggersOnNamespace;       
 
248
                //Special config for this NS?
 
249
                if (isset( $wgCaptchaTriggersOnNamespace[$editPage->mTitle->getNamespace()][$action] ) )
 
250
                        return $wgCaptchaTriggersOnNamespace[$editPage->mTitle->getNamespace()][$action];
 
251
 
 
252
                return ( !empty( $wgCaptchaTriggers[$action] ) ); //Default
 
253
        }
 
254
 
 
255
 
 
256
        /**
 
257
         * @param EditPage $editPage
 
258
         * @param string $newtext
 
259
         * @param string $section
 
260
         * @return bool true if the captcha should run
 
261
         */
 
262
        function shouldCheck( &$editPage, $newtext, $section, $merged = false ) {
 
263
                $this->trigger = '';
 
264
                $title = $editPage->mArticle->getTitle();
 
265
 
 
266
                global $wgUser;
 
267
                if( $wgUser->isAllowed( 'skipcaptcha' ) ) {
 
268
                        wfDebug( "ConfirmEdit: user group allows skipping captcha\n" );
 
269
                        return false;
 
270
                }
 
271
                global $wgCaptchaWhitelistIP;
 
272
                if( !empty( $wgCaptchaWhitelistIP ) ) {
 
273
                        $ip = wfGetIp();
 
274
                        foreach ( $wgCaptchaWhitelistIP as $range ) {
 
275
                                if ( IP::isInRange( $ip, $range ) ) {
 
276
                                        return false;
 
277
                                }
 
278
                        }
 
279
                }
 
280
 
 
281
 
 
282
                global $wgEmailAuthentication, $ceAllowConfirmedEmail;
 
283
                if( $wgEmailAuthentication && $ceAllowConfirmedEmail &&
 
284
                        $wgUser->isEmailConfirmed() ) {
 
285
                        wfDebug( "ConfirmEdit: user has confirmed mail, skipping captcha\n" );
 
286
                        return false;
 
287
                }
 
288
 
 
289
                if( $this->captchaTriggers( $editPage, 'edit' ) ) {
 
290
                        // Check on all edits
 
291
                        global $wgUser;
 
292
                        $this->trigger = sprintf( "edit trigger by '%s' at [[%s]]",
 
293
                                $wgUser->getName(),
 
294
                                $title->getPrefixedText() );
 
295
                        $this->action = 'edit';
 
296
                        wfDebug( "ConfirmEdit: checking all edits...\n" );
 
297
                        return true;
 
298
                }
 
299
 
 
300
                if( $this->captchaTriggers( $editPage, 'create' )  && !$editPage->mTitle->exists() ) {
 
301
                        //Check if creating a page
 
302
                        global $wgUser;
 
303
                        $this->trigger = sprintf( "Create trigger by '%s' at [[%s]]",
 
304
                                $wgUser->getName(),
 
305
                                $title->getPrefixedText() );
 
306
                        $this->action = 'create';
 
307
                        wfDebug( "ConfirmEdit: checking on page creation...\n" );
 
308
                        return true;
 
309
                }
 
310
 
 
311
                if( $this->captchaTriggers( $editPage, 'addurl' ) ) {
 
312
                        // Only check edits that add URLs
 
313
                        if ( $merged ) {
 
314
                                // Get links from the database
 
315
                                $oldLinks = $this->getLinksFromTracker( $title );
 
316
                                // Share a parse operation with Article::doEdit()
 
317
                                $editInfo = $editPage->mArticle->prepareTextForEdit( $newtext );
 
318
                                $newLinks = array_keys( $editInfo->output->getExternalLinks() );
 
319
                        } else {
 
320
                                // Get link changes in the slowest way known to man
 
321
                                $oldtext = $this->loadText( $editPage, $section );
 
322
                                $oldLinks = $this->findLinks( $editPage, $oldtext );
 
323
                                $newLinks = $this->findLinks( $editPage, $newtext );
 
324
                        }
 
325
 
 
326
                        $unknownLinks = array_filter( $newLinks, array( &$this, 'filterLink' ) );
 
327
                        $addedLinks = array_diff( $unknownLinks, $oldLinks );
 
328
                        $numLinks = count( $addedLinks );
 
329
 
 
330
                        if( $numLinks > 0 ) {
 
331
                                global $wgUser;
 
332
                                $this->trigger = sprintf( "%dx url trigger by '%s' at [[%s]]: %s",
 
333
                                        $numLinks,
 
334
                                        $wgUser->getName(),
 
335
                                        $title->getPrefixedText(),
 
336
                                        implode( ", ", $addedLinks ) );
 
337
                                $this->action = 'addurl';
 
338
                                return true;
 
339
                        }
 
340
                }
 
341
 
 
342
                global $wgCaptchaRegexes;
 
343
                if( !empty( $wgCaptchaRegexes ) ) {
 
344
                        // Custom regex checks
 
345
                        $oldtext = $this->loadText( $editPage, $section );
 
346
 
 
347
                        foreach( $wgCaptchaRegexes as $regex ) {
 
348
                                $newMatches = array();
 
349
                                if( preg_match_all( $regex, $newtext, $newMatches ) ) {
 
350
                                        $oldMatches = array();
 
351
                                        preg_match_all( $regex, $oldtext, $oldMatches );
 
352
 
 
353
                                        $addedMatches = array_diff( $newMatches[0], $oldMatches[0] );
 
354
 
 
355
                                        $numHits = count( $addedMatches );
 
356
                                        if( $numHits > 0 ) {
 
357
                                                global $wgUser;
 
358
                                                $this->trigger = sprintf( "%dx %s at [[%s]]: %s",
 
359
                                                        $numHits,
 
360
                                                        $regex,
 
361
                                                        $wgUser->getName(),
 
362
                                                        $title->getPrefixedText(),
 
363
                                                        implode( ", ", $addedMatches ) );
 
364
                                                $this->action = 'edit';
 
365
                                                return true;
 
366
                                        }
 
367
                                }
 
368
                        }
 
369
                }
 
370
 
 
371
                return false;
 
372
        }
 
373
 
 
374
        /**
 
375
         * Filter callback function for URL whitelisting
 
376
         * @param string url to check
 
377
         * @return bool true if unknown, false if whitelisted
 
378
         * @access private
 
379
         */
 
380
        function filterLink( $url ) {
 
381
                global $wgCaptchaWhitelist;
 
382
                $source = wfMsgForContent( 'captcha-addurl-whitelist' );
 
383
 
 
384
                $whitelist = wfEmptyMsg( 'captcha-addurl-whitelist', $source ) 
 
385
                        ? false
 
386
                        : $this->buildRegexes( explode( "\n", $source ) );
 
387
 
 
388
                $cwl = $wgCaptchaWhitelist !== false ? preg_match( $wgCaptchaWhitelist, $url ) : false;
 
389
                $wl  = $whitelist          !== false ? preg_match( $whitelist, $url )          : false;
 
390
 
 
391
                return !( $cwl || $wl );
 
392
        }
 
393
 
 
394
        /**
 
395
         * Build regex from whitelist
 
396
         * @param string lines from [[MediaWiki:Captcha-addurl-whitelist]]
 
397
         * @return string Regex or bool false if whitelist is empty
 
398
         * @access private
 
399
         */
 
400
        function buildRegexes( $lines ) {
 
401
                # Code duplicated from the SpamBlacklist extension (r19197)
 
402
 
 
403
                # Strip comments and whitespace, then remove blanks
 
404
                $lines = array_filter( array_map( 'trim', preg_replace( '/#.*$/', '', $lines ) ) );
 
405
 
 
406
                # No lines, don't make a regex which will match everything
 
407
                if ( count( $lines ) == 0 ) {
 
408
                        wfDebug( "No lines\n" );
 
409
                        return false;
 
410
                } else {
 
411
                        # Make regex
 
412
                        # It's faster using the S modifier even though it will usually only be run once
 
413
                        //$regex = 'http://+[a-z0-9_\-.]*(' . implode( '|', $lines ) . ')';
 
414
                        //return '/' . str_replace( '/', '\/', preg_replace('|\\\*/|', '/', $regex) ) . '/Si';
 
415
                        $regexes = '';
 
416
                        $regexStart = '/^https?:\/\/+[a-z0-9_\-.]*(';
 
417
                        $regexEnd = ')/Si';
 
418
                        $regexMax = 4096;
 
419
                        $build = false;
 
420
                        foreach( $lines as $line ) {
 
421
                                // FIXME: not very robust size check, but should work. :)
 
422
                                if( $build === false ) {
 
423
                                        $build = $line;
 
424
                                } elseif( strlen( $build ) + strlen( $line ) > $regexMax ) {
 
425
                                        $regexes .= $regexStart .
 
426
                                                str_replace( '/', '\/', preg_replace('|\\\*/|', '/', $build) ) .
 
427
                                                $regexEnd;
 
428
                                        $build = $line;
 
429
                                } else {
 
430
                                        $build .= '|' . $line;
 
431
                                }
 
432
                        }
 
433
                        if( $build !== false ) {
 
434
                                $regexes .= $regexStart .
 
435
                                        str_replace( '/', '\/', preg_replace('|\\\*/|', '/', $build) ) .
 
436
                                        $regexEnd;
 
437
                        }
 
438
                        return $regexes;
 
439
                }
 
440
        }
 
441
 
 
442
        /**
 
443
         * Load external links from the externallinks table
 
444
         */
 
445
        function getLinksFromTracker( $title ) {
 
446
                $dbr =& wfGetDB( DB_SLAVE );
 
447
                $id = $title->getArticleId(); // should be zero queries
 
448
                $res = $dbr->select( 'externallinks', array( 'el_to' ), 
 
449
                        array( 'el_from' => $id ), __METHOD__ );
 
450
                $links = array();
 
451
                while ( $row = $dbr->fetchObject( $res ) ) {
 
452
                        $links[] = $row->el_to;
 
453
                }
 
454
                return $links;
 
455
        }
 
456
        
 
457
        /**
 
458
         * Backend function for confirmEdit() and confirmEditAPI()
 
459
         * @return bool false if the CAPTCHA is rejected, true otherwise
 
460
         */
 
461
        private function doConfirmEdit( &$editPage, $newtext, $section, $merged = false ) {
 
462
                if( $this->shouldCheck( $editPage, $newtext, $section, $merged ) ) {
 
463
                        if( $this->passCaptcha() ) {
 
464
                                return true;
 
465
                        } else {
 
466
                                return false;
 
467
                        }
 
468
                } else {
 
469
                        wfDebug( "ConfirmEdit: no need to show captcha.\n" );
 
470
                        return true;
 
471
                }
 
472
        }
 
473
 
 
474
        /**
 
475
         * The main callback run on edit attempts.
 
476
         * @param EditPage $editPage
 
477
         * @param string $newtext
 
478
         * @param string $section
 
479
         * @param bool $merged
 
480
         * @return bool true to continue saving, false to abort and show a captcha form
 
481
         */
 
482
        function confirmEdit( &$editPage, $newtext, $section, $merged = false ) {
 
483
                if( defined('MW_API') ) {
 
484
                        # API mode
 
485
                        # The CAPTCHA was already checked and approved 
 
486
                        return true;
 
487
                }
 
488
                if( !$this->doConfirmEdit( $editPage, $newtext, $section, $merged ) ) {
 
489
                        $editPage->showEditForm( array( &$this, 'editCallback' ) );
 
490
                        return false;
 
491
                }
 
492
                return true;
 
493
        }
 
494
 
 
495
        /**
 
496
         * A more efficient edit filter callback based on the text after section merging
 
497
         * @param EditPage $editPage
 
498
         * @param string $newtext
 
499
         */
 
500
        function confirmEditMerged( &$editPage, $newtext ) {
 
501
                return $this->confirmEdit( $editPage, $newtext, false, true );
 
502
        }
 
503
        
 
504
        
 
505
        function confirmEditAPI( &$editPage, $newtext, &$resultArr) {
 
506
                if( !$this->doConfirmEdit( $editPage, $newtext, false, false ) ) {
 
507
                        $this->addCaptchaAPI($resultArr);
 
508
                        return false;
 
509
                }
 
510
                return true;
 
511
        }
 
512
 
 
513
        /**
 
514
         * Hook for user creation form submissions.
 
515
         * @param User $u
 
516
         * @param string $message
 
517
         * @return bool true to continue, false to abort user creation
 
518
         */
 
519
        function confirmUserCreate( $u, &$message ) {
 
520
                global $wgCaptchaTriggers, $wgUser;
 
521
                if( $wgCaptchaTriggers['createaccount'] ) {
 
522
                        if( $wgUser->isAllowed( 'skipcaptcha' ) ) {
 
523
                                wfDebug( "ConfirmEdit: user group allows skipping captcha on account creation\n" );
 
524
                                return true;
 
525
                        }
 
526
                        $this->trigger = "new account '" . $u->getName() . "'";
 
527
                        if( !$this->passCaptcha() ) {
 
528
                                $message = wfMsg( 'captcha-createaccount-fail' );
 
529
                                return false;
 
530
                        }
 
531
                }
 
532
                return true;
 
533
        }
 
534
        
 
535
        /**
 
536
         * Hook for user login form submissions.
 
537
         * @param User $u
 
538
         * @param string $message
 
539
         * @return bool true to continue, false to abort user creation
 
540
         */
 
541
        function confirmUserLogin( $u, $pass, &$retval ) {
 
542
                if( $this->isBadLoginTriggered() ) {
 
543
                        $this->trigger = "post-badlogin login '" . $u->getName() . "'";
 
544
                        if( !$this->passCaptcha() ) {
 
545
                                $message = wfMsg( 'captcha-badlogin-fail' );
 
546
                                // Emulate a bad-password return to confuse the shit out of attackers
 
547
                                $retval = LoginForm::WRONG_PASS;
 
548
                                return false;
 
549
                        }
 
550
                }
 
551
                return true;
 
552
        }
 
553
 
 
554
        /**
 
555
         * Given a required captcha run, test form input for correct
 
556
         * input on the open session.
 
557
         * @return bool if passed, false if failed or new session
 
558
         */
 
559
        function passCaptcha() {
 
560
                $info = $this->retrieveCaptcha();
 
561
                if( $info ) {
 
562
                        global $wgRequest;
 
563
                        if( $this->keyMatch( $wgRequest->getVal('wpCaptchaWord'), $info ) ) {
 
564
                                $this->log( "passed" );
 
565
                                $this->clearCaptcha( $info );
 
566
                                return true;
 
567
                        } else {
 
568
                                $this->clearCaptcha( $info );
 
569
                                $this->log( "bad form input" );
 
570
                                return false;
 
571
                        }
 
572
                } else {
 
573
                        $this->log( "new captcha session" );
 
574
                        return false;
 
575
                }
 
576
        }
 
577
 
 
578
        /**
 
579
         * Log the status and any triggering info for debugging or statistics
 
580
         * @param string $message
 
581
         */
 
582
        function log( $message ) {
 
583
                wfDebugLog( 'captcha', 'ConfirmEdit: ' . $message . '; ' .  $this->trigger );
 
584
        }
 
585
 
 
586
        /**
 
587
         * Generate a captcha session ID and save the info in PHP's session storage.
 
588
         * (Requires the user to have cookies enabled to get through the captcha.)
 
589
         *
 
590
         * A random ID is used so legit users can make edits in multiple tabs or
 
591
         * windows without being unnecessarily hobbled by a serial order requirement.
 
592
         * Pass the returned id value into the edit form as wpCaptchaId.
 
593
         *
 
594
         * @param array $info data to store
 
595
         * @return string captcha ID key
 
596
         */
 
597
        function storeCaptcha( $info ) {
 
598
                if( !isset( $info['index'] ) ) {
 
599
                        // Assign random index if we're not udpating
 
600
                        $info['index'] = strval( mt_rand() );
 
601
                }
 
602
                $this->storage->store( $info['index'], $info );
 
603
                return $info['index'];
 
604
        }
 
605
 
 
606
        /**
 
607
         * Fetch this session's captcha info.
 
608
         * @return mixed array of info, or false if missing
 
609
         */
 
610
        function retrieveCaptcha() {
 
611
                global $wgRequest;
 
612
                $index = $wgRequest->getVal( 'wpCaptchaId' );
 
613
                return $this->storage->retrieve( $index );
 
614
        }
 
615
 
 
616
        /**
 
617
         * Clear out existing captcha info from the session, to ensure
 
618
         * it can't be reused.
 
619
         */
 
620
        function clearCaptcha( $info ) {
 
621
                $this->storage->clear( $info['index'] );
 
622
        }
 
623
 
 
624
        /**
 
625
         * Retrieve the current version of the page or section being edited...
 
626
         * @param EditPage $editPage
 
627
         * @param string $section
 
628
         * @return string
 
629
         * @access private
 
630
         */
 
631
        function loadText( $editPage, $section ) {
 
632
                $rev = Revision::newFromTitle( $editPage->mTitle );
 
633
                if( is_null( $rev ) ) {
 
634
                        return "";
 
635
                } else {
 
636
                        $text = $rev->getText();
 
637
                        if( $section != '' ) {
 
638
                                return Article::getSection( $text, $section );
 
639
                        } else {
 
640
                                return $text;
 
641
                        }
 
642
                }
 
643
        }
 
644
 
 
645
        /**
 
646
         * Extract a list of all recognized HTTP links in the text.
 
647
         * @param string $text
 
648
         * @return array of strings
 
649
         */
 
650
        function findLinks( &$editpage, $text ) {
 
651
                global $wgParser, $wgUser;
 
652
 
 
653
                $options = new ParserOptions();
 
654
                $text = $wgParser->preSaveTransform( $text, $editpage->mTitle, $wgUser, $options );
 
655
                $out = $wgParser->parse( $text, $editpage->mTitle, $options );
 
656
 
 
657
                return array_keys( $out->getExternalLinks() );
 
658
        }
 
659
 
 
660
        /**
 
661
         * Show a page explaining what this wacky thing is.
 
662
         */
 
663
        function showHelp() {
 
664
                global $wgOut, $ceAllowConfirmedEmail;
 
665
                $wgOut->setPageTitle( wfMsg( 'captchahelp-title' ) );
 
666
                $wgOut->addWikiText( wfMsg( 'captchahelp-text' ) );
 
667
                if ( $this->storage->cookiesNeeded() ) {
 
668
                        $wgOut->addWikiText( wfMsg( 'captchahelp-cookies-needed' ) );
 
669
                }
 
670
        }
 
671
 
 
672
}
 
673
 
 
674
class CaptchaSessionStore {
 
675
        function store( $index, $info ) {
 
676
                $_SESSION['captcha' . $info['index']] = $info;
 
677
        }
 
678
        
 
679
        function retrieve( $index ) {
 
680
                if( isset( $_SESSION['captcha' . $index] ) ) {
 
681
                        return $_SESSION['captcha' . $index];
 
682
                } else {
 
683
                        return false;
 
684
                }
 
685
        }
 
686
        
 
687
        function clear( $index ) {
 
688
                unset( $_SESSION['captcha' . $index] );
 
689
        }
 
690
 
 
691
        function cookiesNeeded() {
 
692
                return true;
 
693
        }
 
694
}
 
695
 
 
696
class CaptchaCacheStore {
 
697
        function store( $index, $info ) {
 
698
                global $wgMemc, $wgCaptchaSessionExpiration;
 
699
                $wgMemc->set( wfMemcKey( 'captcha', $index ), $info,
 
700
                        $wgCaptchaSessionExpiration );
 
701
        }
 
702
 
 
703
        function retrieve( $index ) {
 
704
                global $wgMemc;
 
705
                $info = $wgMemc->get( wfMemcKey( 'captcha', $index ) );
 
706
                if( $info ) {
 
707
                        return $info;
 
708
                } else {
 
709
                        return false;
 
710
                }
 
711
        }
 
712
        
 
713
        function clear( $index ) {
 
714
                global $wgMemc;
 
715
                $wgMemc->delete( wfMemcKey( 'captcha', $index ) );
 
716
        }
 
717
 
 
718
        function cookiesNeeded() {
 
719
                return false;
 
720
        }
 
721
}
 
722