mirror of
https://github.com/splitbrain/dokuwiki-plugin-captcha.git
synced 2025-08-20 16:35:27 +00:00
initial checkin
darcs-hash:20070109215204-7ad00-911102ca792d59b403fea07473621236672a23ee.gz
This commit is contained in:
BIN
VeraSe.ttf
Normal file
BIN
VeraSe.ttf
Normal file
Binary file not shown.
246
action.php
Normal file
246
action.php
Normal file
@ -0,0 +1,246 @@
|
||||
<?php
|
||||
/**
|
||||
* CAPTCHA antispam plugin
|
||||
*
|
||||
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
|
||||
* @author Andreas Gohr <gohr@cosmocode.de>
|
||||
*/
|
||||
|
||||
// must be run within Dokuwiki
|
||||
if(!defined('DOKU_INC')) die();
|
||||
|
||||
if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
|
||||
require_once(DOKU_PLUGIN.'action.php');
|
||||
require_once(DOKU_INC.'inc/blowfish.php');
|
||||
|
||||
class action_plugin_captcha extends DokuWiki_Action_Plugin {
|
||||
|
||||
/**
|
||||
* return some info
|
||||
*/
|
||||
function getInfo(){
|
||||
return array(
|
||||
'author' => 'Andreas Gohr',
|
||||
'email' => 'andi@splitbrain.org',
|
||||
'date' => '2006-12-02',
|
||||
'name' => 'CAPTCHA Plugin',
|
||||
'desc' => 'Use a CAPTCHA challenge to protect the Wiki against automated spam',
|
||||
'url' => 'http://wiki:splitbrain.org/plugin:captcha',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* register the eventhandlers
|
||||
*/
|
||||
function register(&$controller){
|
||||
$controller->register_hook('ACTION_ACT_PREPROCESS',
|
||||
'BEFORE',
|
||||
$this,
|
||||
'handle_act_preprocess',
|
||||
array());
|
||||
|
||||
$controller->register_hook('HTML_EDITFORM_INJECTION',
|
||||
'BEFORE',
|
||||
$this,
|
||||
'handle_editform_injection',
|
||||
array('editform' => true));
|
||||
|
||||
if($this->getConf('regprotect')){
|
||||
$controller->register_hook('ACTION_REGISTER',
|
||||
'BEFORE',
|
||||
$this,
|
||||
'handle_act_register',
|
||||
array());
|
||||
|
||||
$controller->register_hook('HTML_REGISTERFORM_INJECTION',
|
||||
'BEFORE',
|
||||
$this,
|
||||
'handle_editform_injection',
|
||||
array('editform' => false));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will intercept the 'save' action and check for CAPTCHA first.
|
||||
*/
|
||||
function handle_act_preprocess(&$event, $param){
|
||||
if('save' != $this->_act_clean($event->data)) return; // nothing to do for us
|
||||
// do nothing if logged in user and no CAPTCHA required
|
||||
if(!$this->getConf('forusers') && $_SERVER['REMOTE_USER']){
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// compare provided string with decrypted captcha
|
||||
$rand = PMA_blowfish_decrypt($_REQUEST['plugin__captcha_secret'],auth_cookiesalt());
|
||||
$code = $this->_generateCAPTCHA($this->_fixedIdent(),$rand);
|
||||
|
||||
if(!$_REQUEST['plugin__captcha_secret'] ||
|
||||
!$_REQUEST['plugin__captcha'] ||
|
||||
strtoupper($_REQUEST['plugin__captcha']) != $code){
|
||||
// CAPTCHA test failed! Continue to edit instead of saving
|
||||
msg($this->getLang('testfailed'),-1);
|
||||
$event->data = 'preview';
|
||||
}
|
||||
// if we arrive here it was a valid save
|
||||
}
|
||||
|
||||
/**
|
||||
* Will intercept the register process and check for CAPTCHA first.
|
||||
*/
|
||||
function handle_act_register(&$event, $param){
|
||||
// compare provided string with decrypted captcha
|
||||
$rand = PMA_blowfish_decrypt($_REQUEST['plugin__captcha_secret'],auth_cookiesalt());
|
||||
$code = $this->_generateCAPTCHA($this->_fixedIdent(),$rand);
|
||||
|
||||
if(!$_REQUEST['plugin__captcha_secret'] ||
|
||||
!$_REQUEST['plugin__captcha'] ||
|
||||
strtoupper($_REQUEST['plugin__captcha']) != $code){
|
||||
// CAPTCHA test failed! Continue to edit instead of saving
|
||||
msg($this->getLang('testfailed'),-1);
|
||||
$event->preventDefault();
|
||||
$event->stopPropagation();
|
||||
return false;
|
||||
}
|
||||
// if we arrive here it was a valid save
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create the additional fields for the edit form
|
||||
*/
|
||||
function handle_editform_injection(&$event, $param){
|
||||
if($param['editform'] && !$event->data['writable']) return;
|
||||
// do nothing if logged in user and no CAPTCHA required
|
||||
if(!$this->getConf('forusers') && $_SERVER['REMOTE_USER']){
|
||||
return;
|
||||
}
|
||||
|
||||
global $ID;
|
||||
|
||||
$rand = (float) (rand(0,10000))/10000;
|
||||
$code = $this->_generateCAPTCHA($this->_fixedIdent(),$rand);
|
||||
$secret = PMA_blowfish_encrypt($rand,auth_cookiesalt());
|
||||
|
||||
echo '<div id="plugin__captcha_wrapper">';
|
||||
echo '<input type="hidden" name="plugin__captcha_secret" value="'.hsc($secret).'" />';
|
||||
echo '<label for="plugin__captcha">'.$this->getLang('fillcaptcha').'</label> ';
|
||||
echo '<input type="text" size="5" maxlength="5" name="plugin__captcha" id="plugin__captcha" class="edit" /> ';
|
||||
switch($this->getConf('mode')){
|
||||
case 'text':
|
||||
echo $code;
|
||||
break;
|
||||
case 'js':
|
||||
echo '<span id="plugin__captcha_code">'.$code.'</span>';
|
||||
break;
|
||||
case 'image':
|
||||
echo '<img src="'.DOKU_BASE.'lib/plugins/captcha/img.php?secret='.rawurlencode($secret).'&id='.$ID.'" '.
|
||||
' width="'.$this->getConf('width').'" height="'.$this->getConf('height').'" alt="" /> ';
|
||||
break;
|
||||
case 'audio':
|
||||
echo '<img src="'.DOKU_BASE.'lib/plugins/captcha/img.php?secret='.rawurlencode($secret).'&id='.$ID.'" '.
|
||||
' width="'.$this->getConf('width').'" height="'.$this->getConf('height').'" alt="" /> ';
|
||||
echo '<a href="'.DOKU_BASE.'lib/plugins/captcha/wav.php?secret='.rawurlencode($secret).'&id='.$ID.'"'.
|
||||
' class="JSnocheck" title="'.$this->getLang('soundlink').'">';
|
||||
echo '<img src="'.DOKU_BASE.'lib/plugins/captcha/sound.png" width="16" height="16"'.
|
||||
' alt="'.$this->getLang('soundlink').'" /></a>';
|
||||
break;
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a semi-secret fixed string identifying the current page and user
|
||||
*
|
||||
* This string is always the same for the current user when editing the same
|
||||
* page revision.
|
||||
*/
|
||||
function _fixedIdent(){
|
||||
global $ID;
|
||||
$lm = @filemtime(wikiFN($ID));
|
||||
return auth_browseruid().
|
||||
auth_cookiesalt().
|
||||
$ID.$lm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-Sanitize the action command
|
||||
*
|
||||
* Similar to act_clean in action.php but simplified and without
|
||||
* error messages
|
||||
*/
|
||||
function _act_clean($act){
|
||||
// check if the action was given as array key
|
||||
if(is_array($act)){
|
||||
list($act) = array_keys($act);
|
||||
}
|
||||
|
||||
//remove all bad chars
|
||||
$act = strtolower($act);
|
||||
$act = preg_replace('/[^a-z_]+/','',$act);
|
||||
|
||||
return $act;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random 5 char string
|
||||
*
|
||||
* @param $fixed string - the fixed part, any string
|
||||
* @param $rand float - some random number between 0 and 1
|
||||
*/
|
||||
function _generateCAPTCHA($fixed,$rand){
|
||||
$fixed = hexdec(substr(md5($fixed),5,5)); // use part of the md5 to generate an int
|
||||
$rand = $rand * $fixed; // combine both values
|
||||
|
||||
// seed the random generator
|
||||
srand($rand);
|
||||
|
||||
// now create the letters
|
||||
$code = '';
|
||||
for($i=0;$i<5;$i++){
|
||||
$code .= chr(rand(65, 90));
|
||||
}
|
||||
|
||||
// restore a really random seed
|
||||
srand();
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CAPTCHA image
|
||||
*/
|
||||
function _imageCAPTCHA($text){
|
||||
$w = $this->getConf('width');
|
||||
$h = $this->getConf('height');
|
||||
|
||||
// create a white image
|
||||
$img = imagecreate($w, $h);
|
||||
imagecolorallocate($img, 255, 255, 255);
|
||||
|
||||
// add some lines as background noise
|
||||
for ($i = 0; $i < 30; $i++) {
|
||||
$color = imagecolorallocate($img,rand(100, 250),rand(100, 250),rand(100, 250));
|
||||
imageline($img,rand(0,$w),rand(0,$h),rand(0,$w),rand(0,$h),$color);
|
||||
}
|
||||
|
||||
// draw the letters
|
||||
for ($i = 0; $i < strlen($text); $i++){
|
||||
$font = dirname(__FILE__).'/VeraSe.ttf';
|
||||
$color = imagecolorallocate($img, rand(0, 100), rand(0, 100), rand(0, 100));
|
||||
$size = rand(floor($h/1.8),floor($h*0.7));
|
||||
$angle = rand(-35, 35);
|
||||
|
||||
$x = ($w*0.05) + $i * floor($w*0.9/5);
|
||||
$cheight = $size + ($size*0.5);
|
||||
$y = floor($h / 2 + $cheight / 3.8);
|
||||
|
||||
imagettftext($img, $size, $angle, $x, $y, $color, $font, $text[$i]);
|
||||
}
|
||||
|
||||
header("Content-type: image/png");
|
||||
imagepng($img);
|
||||
imagedestroy($img);
|
||||
}
|
||||
}
|
||||
|
12
conf/default.php
Normal file
12
conf/default.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
/**
|
||||
* Options for the CAPTCHA plugin
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
|
||||
$conf['mode'] = 'js';
|
||||
$conf['forusers'] = false;
|
||||
$conf['regprotect'] = true;
|
||||
$conf['width'] = 115;
|
||||
$conf['height'] = 22;
|
13
conf/metadata.php
Normal file
13
conf/metadata.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* Options for the CAPTCHA plugin
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
|
||||
$meta['mode'] = array('multichoice','_choices' => array('js','text','image','audio'));
|
||||
$meta['regprotect'] = array('onoff');
|
||||
$meta['forusers'] = array('onoff');
|
||||
$meta['width'] = array('numeric','_pattern' => '/[0-9]+/');
|
||||
$meta['height'] = array('numeric','_pattern' => '/[0-9]+/');
|
||||
|
22
img.php
Normal file
22
img.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* CAPTCHA antispam plugin - Image generator
|
||||
*
|
||||
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
|
||||
* @author Andreas Gohr <gohr@cosmocode.de>
|
||||
*/
|
||||
|
||||
if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../../').'/');
|
||||
define('NOSESSION',true);
|
||||
define('DOKU_DISABLE_GZIP_OUTPUT', 1);
|
||||
require_once(DOKU_INC.'inc/init.php');
|
||||
require_once(DOKU_INC.'inc/auth.php');
|
||||
require_once(dirname(__FILE__).'/action.php');
|
||||
|
||||
$ID = $_REQUEST['id'];
|
||||
$plugin = new action_plugin_captcha();
|
||||
$rand = PMA_blowfish_decrypt($_REQUEST['secret'],auth_cookiesalt());
|
||||
$code = $plugin->_generateCAPTCHA($plugin->_fixedIdent(),$rand);
|
||||
$plugin->_imageCAPTCHA($code);
|
||||
|
||||
//Setup VIM: ex: et ts=4 enc=utf-8 :
|
4
lang/en/audio/LICENSE
Normal file
4
lang/en/audio/LICENSE
Normal file
@ -0,0 +1,4 @@
|
||||
This work is licensed under the Creative Commons Sampling Plus 1.0 License. To
|
||||
view a copy of this license, visit
|
||||
http://creativecommons.org/licenses/sampling+/1.0/ or send a letter to Creative
|
||||
Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA.
|
13
lang/en/audio/README
Normal file
13
lang/en/audio/README
Normal file
@ -0,0 +1,13 @@
|
||||
Author: Michael Klier <chi@chimeric.de>
|
||||
Link: http://www.chimeric.de/projects/npa
|
||||
Voice: Christian Spellenberg
|
||||
|
||||
These samples represent the NATO phonetical alphabet. They are protected
|
||||
by the Creative Commons Sampling Plus 1.0 License. You are free to use
|
||||
and redistribute these samples under the conditions defined by the
|
||||
license. For further information read the LICENSE file and visit
|
||||
http://www.creativecommons.org.
|
||||
|
||||
Note: The original high quality wave files were downsampled and converted
|
||||
to 8-Bit mono files for distribution with the CAPTCHA plugin. Visit
|
||||
the link above for the original files.
|
BIN
lang/en/audio/a.wav
Normal file
BIN
lang/en/audio/a.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/b.wav
Normal file
BIN
lang/en/audio/b.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/c.wav
Normal file
BIN
lang/en/audio/c.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/d.wav
Normal file
BIN
lang/en/audio/d.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/e.wav
Normal file
BIN
lang/en/audio/e.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/f.wav
Normal file
BIN
lang/en/audio/f.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/g.wav
Normal file
BIN
lang/en/audio/g.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/h.wav
Normal file
BIN
lang/en/audio/h.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/i.wav
Normal file
BIN
lang/en/audio/i.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/j.wav
Normal file
BIN
lang/en/audio/j.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/k.wav
Normal file
BIN
lang/en/audio/k.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/l.wav
Normal file
BIN
lang/en/audio/l.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/m.wav
Normal file
BIN
lang/en/audio/m.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/n.wav
Normal file
BIN
lang/en/audio/n.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/o.wav
Normal file
BIN
lang/en/audio/o.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/p.wav
Normal file
BIN
lang/en/audio/p.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/q.wav
Normal file
BIN
lang/en/audio/q.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/r.wav
Normal file
BIN
lang/en/audio/r.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/s.wav
Normal file
BIN
lang/en/audio/s.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/t.wav
Normal file
BIN
lang/en/audio/t.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/u.wav
Normal file
BIN
lang/en/audio/u.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/v.wav
Normal file
BIN
lang/en/audio/v.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/w.wav
Normal file
BIN
lang/en/audio/w.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/x.wav
Normal file
BIN
lang/en/audio/x.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/y.wav
Normal file
BIN
lang/en/audio/y.wav
Normal file
Binary file not shown.
BIN
lang/en/audio/z.wav
Normal file
BIN
lang/en/audio/z.wav
Normal file
Binary file not shown.
11
lang/en/lang.php
Normal file
11
lang/en/lang.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* English language file
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
|
||||
$lang['testfailed'] = "Sorry, but the CAPTCHA wasn't answered correctly. Maybe you're not human at all?";
|
||||
$lang['fillcaptcha'] = "Please fill all the letters into the box to prove you're human.";
|
||||
$lang['soundlink'] = "If you can't read the letters on the image, download this .wav file to get them read to you.";
|
||||
|
18
lang/en/settings.php
Normal file
18
lang/en/settings.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
/**
|
||||
* English language file
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
|
||||
$lang['mode'] = "Which type of CAPTCHA to use?";
|
||||
$lang['mode_o_js'] = "Text (prefilled with JavaScript)";
|
||||
$lang['mode_o_text'] = "Text (manual only)";
|
||||
$lang['mode_o_image'] = "Image (bad accessibility)";
|
||||
$lang['mode_o_audio'] = "Image (better accessibility)";
|
||||
|
||||
$lang['regprotect'] = "Protect the registration form as well?";
|
||||
$lang['forusers'] = "Use CAPTCHA for logged in users, too?";
|
||||
$lang['width'] = "Width of the CAPTCHA image (pixel)";
|
||||
$lang['height'] = "Height of the CAPTCHA image (pixel)";
|
||||
|
12
script.js
Normal file
12
script.js
Normal file
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Autofill and hide the whole captcha stuff in the simple JS mode
|
||||
*/
|
||||
addInitEvent(function(){
|
||||
var code = $('plugin__captcha_code');
|
||||
if(!code) return;
|
||||
|
||||
var box = $('plugin__captcha');
|
||||
box.value=code.innerHTML;
|
||||
|
||||
$('plugin__captcha_wrapper').style.display = 'none';
|
||||
});
|
5
style.css
Normal file
5
style.css
Normal file
@ -0,0 +1,5 @@
|
||||
.dokuwiki #plugin__captcha_wrapper img {
|
||||
margin: 1px;
|
||||
vertical-align: bottom;
|
||||
border: 1px solid __border__;
|
||||
}
|
77
wav.php
Normal file
77
wav.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/**
|
||||
* CAPTCHA antispam plugin - sound generator
|
||||
*
|
||||
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
|
||||
* @author Andreas Gohr <gohr@cosmocode.de>
|
||||
*/
|
||||
|
||||
if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../../').'/');
|
||||
define('NOSESSION',true);
|
||||
define('DOKU_DISABLE_GZIP_OUTPUT', 1);
|
||||
require_once(DOKU_INC.'inc/init.php');
|
||||
require_once(DOKU_INC.'inc/auth.php');
|
||||
require_once(dirname(__FILE__).'/action.php');
|
||||
|
||||
$ID = $_REQUEST['id'];
|
||||
$plugin = new action_plugin_captcha();
|
||||
$rand = PMA_blowfish_decrypt($_REQUEST['secret'],auth_cookiesalt());
|
||||
$code = strtolower($plugin->_generateCAPTCHA($plugin->_fixedIdent(),$rand));
|
||||
|
||||
// prepare an array of wavfiles
|
||||
$lc = dirname(__FILE__).'/lang/'.$conf['lang'].'/audio/';
|
||||
$en = dirname(__FILE__).'/lang/en/audio/';
|
||||
$wavs = array();
|
||||
for($i=0;$i<5;$i++){
|
||||
$file = $lc.$code{$i}.'.wav';
|
||||
if(!@file_exists($file)) $file = $en.$code{$i}.'.wav';
|
||||
$wavs[] = $file;
|
||||
}
|
||||
|
||||
header('Content-type: audio/x-wav');
|
||||
header('Content-Disposition: attachment;filename=captcha.wav');
|
||||
|
||||
echo joinwavs($wavs);
|
||||
|
||||
|
||||
/**
|
||||
* Join multiple wav files
|
||||
*
|
||||
* All wave files need to have the same format and need to be uncompressed.
|
||||
* The headers of the last file will be used (with recalculated datasize
|
||||
* of course)
|
||||
*
|
||||
* @link http://ccrma.stanford.edu/CCRMA/Courses/422/projects/WaveFormat/
|
||||
* @link http://www.thescripts.com/forum/thread3770.html
|
||||
*/
|
||||
function joinwavs($wavs){
|
||||
$fields = join('/',array( 'H8ChunkID', 'VChunkSize', 'H8Format',
|
||||
'H8Subchunk1ID', 'VSubchunk1Size',
|
||||
'vAudioFormat', 'vNumChannels', 'VSampleRate',
|
||||
'VByteRate', 'vBlockAlign', 'vBitsPerSample' ));
|
||||
|
||||
$data = '';
|
||||
foreach($wavs as $wav){
|
||||
$fp = fopen($wav,'rb');
|
||||
$header = fread($fp,36);
|
||||
$info = unpack($fields,$header);
|
||||
|
||||
// read optional extra stuff
|
||||
if($info['Subchunk1Size'] > 16){
|
||||
$header .= fread($fp,($info['Subchunk1Size']-16));
|
||||
}
|
||||
|
||||
// read SubChunk2ID
|
||||
$header .= fread($fp,4);
|
||||
|
||||
// read Subchunk2Size
|
||||
$size = unpack('vsize',fread($fp, 4));
|
||||
$size = $size['size'];
|
||||
|
||||
// read data
|
||||
$data .= fread($fp,$size);
|
||||
}
|
||||
|
||||
return $header.pack('V',strlen($data)).$data;
|
||||
}
|
||||
|
Reference in New Issue
Block a user