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

« back to all changes in this revision

Viewing changes to include/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
 
                global $wgTitle;
484
 
                if( is_null( $wgTitle ) ) {
485
 
                        # API mode
486
 
                        # The CAPTCHA was already checked and approved 
487
 
                        return true;
488
 
                }
489
 
                if( !$this->doConfirmEdit( $editPage, $newtext, $section, $merged ) ) {
490
 
                        $editPage->showEditForm( array( &$this, 'editCallback' ) );
491
 
                        return false;
492
 
                }
493
 
                return true;
494
 
        }
495
 
 
496
 
        /**
497
 
         * A more efficient edit filter callback based on the text after section merging
498
 
         * @param EditPage $editPage
499
 
         * @param string $newtext
500
 
         */
501
 
        function confirmEditMerged( &$editPage, $newtext ) {
502
 
                return $this->confirmEdit( $editPage, $newtext, false, true );
503
 
        }
504
 
        
505
 
        
506
 
        function confirmEditAPI( &$editPage, $newtext, &$resultArr) {
507
 
                if( !$this->doConfirmEdit( $editPage, $newtext, false, false ) ) {
508
 
                        $this->addCaptchaAPI($resultArr);
509
 
                        return false;
510
 
                }
511
 
                return true;
512
 
        }
513
 
 
514
 
        /**
515
 
         * Hook for user creation form submissions.
516
 
         * @param User $u
517
 
         * @param string $message
518
 
         * @return bool true to continue, false to abort user creation
519
 
         */
520
 
        function confirmUserCreate( $u, &$message ) {
521
 
                global $wgCaptchaTriggers, $wgUser;
522
 
                if( $wgCaptchaTriggers['createaccount'] ) {
523
 
                        if( $wgUser->isAllowed( 'skipcaptcha' ) ) {
524
 
                                wfDebug( "ConfirmEdit: user group allows skipping captcha on account creation\n" );
525
 
                                return true;
526
 
                        }
527
 
                        $this->trigger = "new account '" . $u->getName() . "'";
528
 
                        if( !$this->passCaptcha() ) {
529
 
                                $message = wfMsg( 'captcha-createaccount-fail' );
530
 
                                return false;
531
 
                        }
532
 
                }
533
 
                return true;
534
 
        }
535
 
        
536
 
        /**
537
 
         * Hook for user login form submissions.
538
 
         * @param User $u
539
 
         * @param string $message
540
 
         * @return bool true to continue, false to abort user creation
541
 
         */
542
 
        function confirmUserLogin( $u, $pass, &$retval ) {
543
 
                if( $this->isBadLoginTriggered() ) {
544
 
                        $this->trigger = "post-badlogin login '" . $u->getName() . "'";
545
 
                        if( !$this->passCaptcha() ) {
546
 
                                $message = wfMsg( 'captcha-badlogin-fail' );
547
 
                                // Emulate a bad-password return to confuse the shit out of attackers
548
 
                                $retval = LoginForm::WRONG_PASS;
549
 
                                return false;
550
 
                        }
551
 
                }
552
 
                return true;
553
 
        }
554
 
 
555
 
        /**
556
 
         * Given a required captcha run, test form input for correct
557
 
         * input on the open session.
558
 
         * @return bool if passed, false if failed or new session
559
 
         */
560
 
        function passCaptcha() {
561
 
                $info = $this->retrieveCaptcha();
562
 
                if( $info ) {
563
 
                        global $wgRequest;
564
 
                        if( $this->keyMatch( $wgRequest->getVal('wpCaptchaWord'), $info ) ) {
565
 
                                $this->log( "passed" );
566
 
                                $this->clearCaptcha( $info );
567
 
                                return true;
568
 
                        } else {
569
 
                                $this->clearCaptcha( $info );
570
 
                                $this->log( "bad form input" );
571
 
                                return false;
572
 
                        }
573
 
                } else {
574
 
                        $this->log( "new captcha session" );
575
 
                        return false;
576
 
                }
577
 
        }
578
 
 
579
 
        /**
580
 
         * Log the status and any triggering info for debugging or statistics
581
 
         * @param string $message
582
 
         */
583
 
        function log( $message ) {
584
 
                wfDebugLog( 'captcha', 'ConfirmEdit: ' . $message . '; ' .  $this->trigger );
585
 
        }
586
 
 
587
 
        /**
588
 
         * Generate a captcha session ID and save the info in PHP's session storage.
589
 
         * (Requires the user to have cookies enabled to get through the captcha.)
590
 
         *
591
 
         * A random ID is used so legit users can make edits in multiple tabs or
592
 
         * windows without being unnecessarily hobbled by a serial order requirement.
593
 
         * Pass the returned id value into the edit form as wpCaptchaId.
594
 
         *
595
 
         * @param array $info data to store
596
 
         * @return string captcha ID key
597
 
         */
598
 
        function storeCaptcha( $info ) {
599
 
                if( !isset( $info['index'] ) ) {
600
 
                        // Assign random index if we're not udpating
601
 
                        $info['index'] = strval( mt_rand() );
602
 
                }
603
 
                $this->storage->store( $info['index'], $info );
604
 
                return $info['index'];
605
 
        }
606
 
 
607
 
        /**
608
 
         * Fetch this session's captcha info.
609
 
         * @return mixed array of info, or false if missing
610
 
         */
611
 
        function retrieveCaptcha() {
612
 
                global $wgRequest;
613
 
                $index = $wgRequest->getVal( 'wpCaptchaId' );
614
 
                return $this->storage->retrieve( $index );
615
 
        }
616
 
 
617
 
        /**
618
 
         * Clear out existing captcha info from the session, to ensure
619
 
         * it can't be reused.
620
 
         */
621
 
        function clearCaptcha( $info ) {
622
 
                $this->storage->clear( $info['index'] );
623
 
        }
624
 
 
625
 
        /**
626
 
         * Retrieve the current version of the page or section being edited...
627
 
         * @param EditPage $editPage
628
 
         * @param string $section
629
 
         * @return string
630
 
         * @access private
631
 
         */
632
 
        function loadText( $editPage, $section ) {
633
 
                $rev = Revision::newFromTitle( $editPage->mTitle );
634
 
                if( is_null( $rev ) ) {
635
 
                        return "";
636
 
                } else {
637
 
                        $text = $rev->getText();
638
 
                        if( $section != '' ) {
639
 
                                return Article::getSection( $text, $section );
640
 
                        } else {
641
 
                                return $text;
642
 
                        }
643
 
                }
644
 
        }
645
 
 
646
 
        /**
647
 
         * Extract a list of all recognized HTTP links in the text.
648
 
         * @param string $text
649
 
         * @return array of strings
650
 
         */
651
 
        function findLinks( &$editpage, $text ) {
652
 
                global $wgParser, $wgUser;
653
 
 
654
 
                $options = new ParserOptions();
655
 
                $text = $wgParser->preSaveTransform( $text, $editpage->mTitle, $wgUser, $options );
656
 
                $out = $wgParser->parse( $text, $editpage->mTitle, $options );
657
 
 
658
 
                return array_keys( $out->getExternalLinks() );
659
 
        }
660
 
 
661
 
        /**
662
 
         * Show a page explaining what this wacky thing is.
663
 
         */
664
 
        function showHelp() {
665
 
                global $wgOut, $ceAllowConfirmedEmail;
666
 
                $wgOut->setPageTitle( wfMsg( 'captchahelp-title' ) );
667
 
                $wgOut->addWikiText( wfMsg( 'captchahelp-text' ) );
668
 
                if ( $this->storage->cookiesNeeded() ) {
669
 
                        $wgOut->addWikiText( wfMsg( 'captchahelp-cookies-needed' ) );
670
 
                }
671
 
        }
672
 
 
673
 
}
674
 
 
675
 
class CaptchaSessionStore {
676
 
        function store( $index, $info ) {
677
 
                $_SESSION['captcha' . $info['index']] = $info;
678
 
        }
679
 
        
680
 
        function retrieve( $index ) {
681
 
                if( isset( $_SESSION['captcha' . $index] ) ) {
682
 
                        return $_SESSION['captcha' . $index];
683
 
                } else {
684
 
                        return false;
685
 
                }
686
 
        }
687
 
        
688
 
        function clear( $index ) {
689
 
                unset( $_SESSION['captcha' . $index] );
690
 
        }
691
 
 
692
 
        function cookiesNeeded() {
693
 
                return true;
694
 
        }
695
 
}
696
 
 
697
 
class CaptchaCacheStore {
698
 
        function store( $index, $info ) {
699
 
                global $wgMemc, $wgCaptchaSessionExpiration;
700
 
                $wgMemc->set( wfMemcKey( 'captcha', $index ), $info,
701
 
                        $wgCaptchaSessionExpiration );
702
 
        }
703
 
 
704
 
        function retrieve( $index ) {
705
 
                global $wgMemc;
706
 
                $info = $wgMemc->get( wfMemcKey( 'captcha', $index ) );
707
 
                if( $info ) {
708
 
                        return $info;
709
 
                } else {
710
 
                        return false;
711
 
                }
712
 
        }
713
 
        
714
 
        function clear( $index ) {
715
 
                global $wgMemc;
716
 
                $wgMemc->delete( wfMemcKey( 'captcha', $index ) );
717
 
        }
718
 
 
719
 
        function cookiesNeeded() {
720
 
                return false;
721
 
        }
722
 
}
723