142a27035SAndreas Gohr<?php 242a27035SAndreas Gohr/** 342a27035SAndreas Gohr * CAPTCHA antispam plugin 442a27035SAndreas Gohr * 542a27035SAndreas Gohr * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 642a27035SAndreas Gohr * @author Andreas Gohr <gohr@cosmocode.de> 742a27035SAndreas Gohr */ 842a27035SAndreas Gohr 942a27035SAndreas Gohr// must be run within Dokuwiki 1042a27035SAndreas Gohrif(!defined('DOKU_INC')) die(); 1142a27035SAndreas Gohrif(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/'); 127218f96cSAndreas Gohr 1342a27035SAndreas Gohrclass action_plugin_captcha extends DokuWiki_Action_Plugin { 1442a27035SAndreas Gohr 1542a27035SAndreas Gohr /** 1642a27035SAndreas Gohr * register the eventhandlers 1742a27035SAndreas Gohr */ 187218f96cSAndreas Gohr public function register(Doku_Event_Handler $controller) { 197218f96cSAndreas Gohr // check CAPTCHA success 20c2695b40SAndreas Gohr $controller->register_hook( 21c2695b40SAndreas Gohr 'ACTION_ACT_PREPROCESS', 2242a27035SAndreas Gohr 'BEFORE', 2342a27035SAndreas Gohr $this, 247218f96cSAndreas Gohr 'handle_captcha_input', 25c2695b40SAndreas Gohr array() 26c2695b40SAndreas Gohr ); 2742a27035SAndreas Gohr 287218f96cSAndreas Gohr // inject in edit form 29c2695b40SAndreas Gohr $controller->register_hook( 30c2695b40SAndreas Gohr 'HTML_EDITFORM_OUTPUT', 3147afabe6SAndreas Gohr 'BEFORE', 3247afabe6SAndreas Gohr $this, 337218f96cSAndreas Gohr 'handle_form_output', 347218f96cSAndreas Gohr array() 35c2695b40SAndreas Gohr ); 3642a27035SAndreas Gohr 377218f96cSAndreas Gohr // inject in user registration 38c2695b40SAndreas Gohr $controller->register_hook( 39c2695b40SAndreas Gohr 'HTML_REGISTERFORM_OUTPUT', 4047afabe6SAndreas Gohr 'BEFORE', 4147afabe6SAndreas Gohr $this, 427218f96cSAndreas Gohr 'handle_form_output', 437218f96cSAndreas Gohr array() 44c2695b40SAndreas Gohr ); 45f74276b8SAndreas Gohr 46f74276b8SAndreas Gohr // inject in password reset 47f74276b8SAndreas Gohr $controller->register_hook( 48f74276b8SAndreas Gohr 'HTML_RESENDPWDFORM_OUTPUT', 49f74276b8SAndreas Gohr 'BEFORE', 50f74276b8SAndreas Gohr $this, 51f74276b8SAndreas Gohr 'handle_form_output', 52f74276b8SAndreas Gohr array() 53f74276b8SAndreas Gohr ); 54643f15bdSAndreas Gohr 55643f15bdSAndreas Gohr if($this->getConf('loginprotect')) { 56643f15bdSAndreas Gohr // inject in login form 57643f15bdSAndreas Gohr $controller->register_hook( 58643f15bdSAndreas Gohr 'HTML_LOGINFORM_OUTPUT', 59643f15bdSAndreas Gohr 'BEFORE', 60643f15bdSAndreas Gohr $this, 61643f15bdSAndreas Gohr 'handle_form_output', 62643f15bdSAndreas Gohr array() 63643f15bdSAndreas Gohr ); 64643f15bdSAndreas Gohr // check on login 65643f15bdSAndreas Gohr $controller->register_hook( 66643f15bdSAndreas Gohr 'AUTH_LOGIN_CHECK', 67643f15bdSAndreas Gohr 'BEFORE', 68643f15bdSAndreas Gohr $this, 69643f15bdSAndreas Gohr 'handle_login', 70643f15bdSAndreas Gohr array() 71643f15bdSAndreas Gohr ); 72643f15bdSAndreas Gohr } 73cde3ece1SAndreas Gohr 74cde3ece1SAndreas Gohr // clean up captcha cookies 75cde3ece1SAndreas Gohr $controller->register_hook( 76cde3ece1SAndreas Gohr 'INDEXER_TASKS_RUN', 77cde3ece1SAndreas Gohr 'AFTER', 78cde3ece1SAndreas Gohr $this, 79cde3ece1SAndreas Gohr 'handle_indexer', 80cde3ece1SAndreas Gohr array() 81cde3ece1SAndreas Gohr ); 8242a27035SAndreas Gohr } 8342a27035SAndreas Gohr 8442a27035SAndreas Gohr /** 85bd26d35bSAndreas Gohr * Check if the current mode should be handled by CAPTCHA 86bd26d35bSAndreas Gohr * 87f74276b8SAndreas Gohr * Note: checking needs to be done when a form has been submitted, not when the form 88f74276b8SAndreas Gohr * is shown for the first time. Except for the editing process this is not determined 89f74276b8SAndreas Gohr * by $act alone but needs to inspect other input variables. 90f74276b8SAndreas Gohr * 91bd26d35bSAndreas Gohr * @param string $act cleaned action mode 92bd26d35bSAndreas Gohr * @return bool 93bd26d35bSAndreas Gohr */ 94f74276b8SAndreas Gohr protected function needs_checking($act) { 95bd26d35bSAndreas Gohr global $INPUT; 96bd26d35bSAndreas Gohr 97bd26d35bSAndreas Gohr switch($act) { 98bd26d35bSAndreas Gohr case 'save': 99bd26d35bSAndreas Gohr return true; 100bd26d35bSAndreas Gohr case 'register': 101f74276b8SAndreas Gohr case 'resendpwd': 102bd26d35bSAndreas Gohr return $INPUT->bool('save'); 103643f15bdSAndreas Gohr case 'login': 104643f15bdSAndreas Gohr // we do not handle this here, but in handle_login() 105bd26d35bSAndreas Gohr default: 106bd26d35bSAndreas Gohr return false; 107bd26d35bSAndreas Gohr } 108bd26d35bSAndreas Gohr } 109bd26d35bSAndreas Gohr 110bd26d35bSAndreas Gohr /** 111bd26d35bSAndreas Gohr * Aborts the given mode 112bd26d35bSAndreas Gohr * 113bd26d35bSAndreas Gohr * Aborting depends on the mode. It might unset certain input parameters or simply switch 114bd26d35bSAndreas Gohr * the mode to something else (giving as return which needs to be passed back to the 115bd26d35bSAndreas Gohr * ACTION_ACT_PREPROCESS event) 116bd26d35bSAndreas Gohr * 117bd26d35bSAndreas Gohr * @param string $act cleaned action mode 118bd26d35bSAndreas Gohr * @return string the new mode to use 119bd26d35bSAndreas Gohr */ 120bd26d35bSAndreas Gohr protected function abort_action($act) { 121bd26d35bSAndreas Gohr global $INPUT; 122bd26d35bSAndreas Gohr 123bd26d35bSAndreas Gohr switch($act) { 124bd26d35bSAndreas Gohr case 'save': 125bd26d35bSAndreas Gohr return 'preview'; 126bd26d35bSAndreas Gohr case 'register': 127f74276b8SAndreas Gohr case 'resendpwd': 128bd26d35bSAndreas Gohr $INPUT->post->set('save', false); 129f74276b8SAndreas Gohr return $act; 130643f15bdSAndreas Gohr case 'login': 131643f15bdSAndreas Gohr // we do not handle this here, but in handle_login() 132bd26d35bSAndreas Gohr default: 133bd26d35bSAndreas Gohr return $act; 134bd26d35bSAndreas Gohr } 135bd26d35bSAndreas Gohr } 136bd26d35bSAndreas Gohr 137bd26d35bSAndreas Gohr /** 138643f15bdSAndreas Gohr * Handles CAPTCHA check in login 139643f15bdSAndreas Gohr * 140643f15bdSAndreas Gohr * Logins happen very early in the DokuWiki lifecycle, so we have to intercept them 141643f15bdSAndreas Gohr * in their own event. 142643f15bdSAndreas Gohr * 143643f15bdSAndreas Gohr * @param Doku_Event $event 144643f15bdSAndreas Gohr * @param $param 145643f15bdSAndreas Gohr */ 146643f15bdSAndreas Gohr public function handle_login(Doku_Event $event, $param) { 147643f15bdSAndreas Gohr global $INPUT; 148643f15bdSAndreas Gohr if(!$this->getConf('loginprotect')) return; // no protection wanted 149643f15bdSAndreas Gohr if(!$INPUT->bool('u')) return; // this login was not triggered by a form 150643f15bdSAndreas Gohr 151643f15bdSAndreas Gohr // we need to have $ID set for the captcha check 152643f15bdSAndreas Gohr global $ID; 153643f15bdSAndreas Gohr $ID = getID(); 154643f15bdSAndreas Gohr 155643f15bdSAndreas Gohr /** @var helper_plugin_captcha $helper */ 156643f15bdSAndreas Gohr $helper = plugin_load('helper', 'captcha'); 157643f15bdSAndreas Gohr if(!$helper->check()) { 158643f15bdSAndreas Gohr $event->data['silent'] = true; // we have our own message 159643f15bdSAndreas Gohr $event->result = false; // login fail 160643f15bdSAndreas Gohr $event->preventDefault(); 161643f15bdSAndreas Gohr $event->stopPropagation(); 162643f15bdSAndreas Gohr } 163643f15bdSAndreas Gohr } 164643f15bdSAndreas Gohr 165643f15bdSAndreas Gohr /** 166643f15bdSAndreas Gohr * Intercept all actions and check for CAPTCHA first. 16742a27035SAndreas Gohr */ 1687218f96cSAndreas Gohr public function handle_captcha_input(Doku_Event $event, $param) { 1697218f96cSAndreas Gohr $act = act_clean($event->data); 170f74276b8SAndreas Gohr if(!$this->needs_checking($act)) return; 17193f66506SAndreas Gohr 17242a27035SAndreas Gohr // do nothing if logged in user and no CAPTCHA required 17342a27035SAndreas Gohr if(!$this->getConf('forusers') && $_SERVER['REMOTE_USER']) { 17442a27035SAndreas Gohr return; 17542a27035SAndreas Gohr } 17642a27035SAndreas Gohr 17777e00bf9SAndreas Gohr // check captcha 1787218f96cSAndreas Gohr /** @var helper_plugin_captcha $helper */ 17977e00bf9SAndreas Gohr $helper = plugin_load('helper', 'captcha'); 18077e00bf9SAndreas Gohr if(!$helper->check()) { 181bd26d35bSAndreas Gohr $event->data = $this->abort_action($act); 18242a27035SAndreas Gohr } 18342a27035SAndreas Gohr } 18442a27035SAndreas Gohr 18542a27035SAndreas Gohr /** 1867218f96cSAndreas Gohr * Inject the CAPTCHA in a DokuForm 18742a27035SAndreas Gohr */ 1887218f96cSAndreas Gohr public function handle_form_output(Doku_Event $event, $param) { 18947afabe6SAndreas Gohr // get position of submit button 19047afabe6SAndreas Gohr $pos = $event->data->findElementByAttribute('type', 'submit'); 19147afabe6SAndreas Gohr if(!$pos) return; // no button -> source view mode 19247afabe6SAndreas Gohr 19342a27035SAndreas Gohr // do nothing if logged in user and no CAPTCHA required 19442a27035SAndreas Gohr if(!$this->getConf('forusers') && $_SERVER['REMOTE_USER']) { 19542a27035SAndreas Gohr return; 19642a27035SAndreas Gohr } 19742a27035SAndreas Gohr 19877e00bf9SAndreas Gohr // get the CAPTCHA 1997218f96cSAndreas Gohr /** @var helper_plugin_captcha $helper */ 20077e00bf9SAndreas Gohr $helper = plugin_load('helper', 'captcha'); 20177e00bf9SAndreas Gohr $out = $helper->getHTML(); 20247afabe6SAndreas Gohr 2037218f96cSAndreas Gohr // new wiki - insert after the submit button 2047218f96cSAndreas Gohr $event->data->insertElement($pos + 1, $out); 20542a27035SAndreas Gohr } 20642a27035SAndreas Gohr 207cde3ece1SAndreas Gohr /** 208cde3ece1SAndreas Gohr * Clean cookies once per day 209cde3ece1SAndreas Gohr */ 210cde3ece1SAndreas Gohr public function handle_indexer(Doku_Event $event, $param) { 211cde3ece1SAndreas Gohr $lastrun = getCacheName('captcha', '.captcha'); 212cde3ece1SAndreas Gohr $last = @filemtime($lastrun); 213cde3ece1SAndreas Gohr if(time() - $last < 24 * 60 * 60) return; 214cde3ece1SAndreas Gohr 215cde3ece1SAndreas Gohr /** @var helper_plugin_captcha $helper */ 216cde3ece1SAndreas Gohr $helper = plugin_load('helper', 'captcha'); 217cde3ece1SAndreas Gohr $helper->_cleanCaptchaCookies(); 218*5d59bd09SAndreas Gohr touch($lastrun); 219cde3ece1SAndreas Gohr 220cde3ece1SAndreas Gohr $event->preventDefault(); 221cde3ece1SAndreas Gohr $event->stopPropagation(); 222cde3ece1SAndreas Gohr } 22342a27035SAndreas Gohr} 22442a27035SAndreas Gohr 225