1<?php 2/** 3 * CAPTCHA antispam plugin 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Andreas Gohr <gohr@cosmocode.de> 7 */ 8 9// must be run within Dokuwiki 10if(!defined('DOKU_INC')) die(); 11if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/'); 12 13class action_plugin_captcha extends DokuWiki_Action_Plugin { 14 15 /** 16 * register the eventhandlers 17 */ 18 public function register(Doku_Event_Handler $controller) { 19 // check CAPTCHA success 20 $controller->register_hook( 21 'ACTION_ACT_PREPROCESS', 22 'BEFORE', 23 $this, 24 'handle_captcha_input', 25 array() 26 ); 27 28 // inject in edit form 29 $controller->register_hook( 30 'HTML_EDITFORM_OUTPUT', 31 'BEFORE', 32 $this, 33 'handle_form_output', 34 array() 35 ); 36 37 // inject in user registration 38 $controller->register_hook( 39 'HTML_REGISTERFORM_OUTPUT', 40 'BEFORE', 41 $this, 42 'handle_form_output', 43 array() 44 ); 45 46 // inject in password reset 47 $controller->register_hook( 48 'HTML_RESENDPWDFORM_OUTPUT', 49 'BEFORE', 50 $this, 51 'handle_form_output', 52 array() 53 ); 54 55 if($this->getConf('loginprotect')) { 56 // inject in login form 57 $controller->register_hook( 58 'HTML_LOGINFORM_OUTPUT', 59 'BEFORE', 60 $this, 61 'handle_form_output', 62 array() 63 ); 64 // check on login 65 $controller->register_hook( 66 'AUTH_LOGIN_CHECK', 67 'BEFORE', 68 $this, 69 'handle_login', 70 array() 71 ); 72 } 73 74 // clean up captcha cookies 75 $controller->register_hook( 76 'INDEXER_TASKS_RUN', 77 'AFTER', 78 $this, 79 'handle_indexer', 80 array() 81 ); 82 } 83 84 /** 85 * Check if the current mode should be handled by CAPTCHA 86 * 87 * Note: checking needs to be done when a form has been submitted, not when the form 88 * is shown for the first time. Except for the editing process this is not determined 89 * by $act alone but needs to inspect other input variables. 90 * 91 * @param string $act cleaned action mode 92 * @return bool 93 */ 94 protected function needs_checking($act) { 95 global $INPUT; 96 97 switch($act) { 98 case 'save': 99 return true; 100 case 'register': 101 case 'resendpwd': 102 return $INPUT->bool('save'); 103 case 'login': 104 // we do not handle this here, but in handle_login() 105 default: 106 return false; 107 } 108 } 109 110 /** 111 * Aborts the given mode 112 * 113 * Aborting depends on the mode. It might unset certain input parameters or simply switch 114 * the mode to something else (giving as return which needs to be passed back to the 115 * ACTION_ACT_PREPROCESS event) 116 * 117 * @param string $act cleaned action mode 118 * @return string the new mode to use 119 */ 120 protected function abort_action($act) { 121 global $INPUT; 122 123 switch($act) { 124 case 'save': 125 return 'preview'; 126 case 'register': 127 case 'resendpwd': 128 $INPUT->post->set('save', false); 129 return $act; 130 case 'login': 131 // we do not handle this here, but in handle_login() 132 default: 133 return $act; 134 } 135 } 136 137 /** 138 * Handles CAPTCHA check in login 139 * 140 * Logins happen very early in the DokuWiki lifecycle, so we have to intercept them 141 * in their own event. 142 * 143 * @param Doku_Event $event 144 * @param $param 145 */ 146 public function handle_login(Doku_Event $event, $param) { 147 global $INPUT; 148 if(!$this->getConf('loginprotect')) return; // no protection wanted 149 if(!$INPUT->bool('u')) return; // this login was not triggered by a form 150 151 // we need to have $ID set for the captcha check 152 global $ID; 153 $ID = getID(); 154 155 /** @var helper_plugin_captcha $helper */ 156 $helper = plugin_load('helper', 'captcha'); 157 if(!$helper->check()) { 158 $event->data['silent'] = true; // we have our own message 159 $event->result = false; // login fail 160 $event->preventDefault(); 161 $event->stopPropagation(); 162 } 163 } 164 165 /** 166 * Intercept all actions and check for CAPTCHA first. 167 */ 168 public function handle_captcha_input(Doku_Event $event, $param) { 169 $act = act_clean($event->data); 170 if(!$this->needs_checking($act)) return; 171 172 // do nothing if logged in user and no CAPTCHA required 173 if(!$this->getConf('forusers') && $_SERVER['REMOTE_USER']) { 174 return; 175 } 176 177 // check captcha 178 /** @var helper_plugin_captcha $helper */ 179 $helper = plugin_load('helper', 'captcha'); 180 if(!$helper->check()) { 181 $event->data = $this->abort_action($act); 182 } 183 } 184 185 /** 186 * Inject the CAPTCHA in a DokuForm 187 */ 188 public function handle_form_output(Doku_Event $event, $param) { 189 // get position of submit button 190 $pos = $event->data->findElementByAttribute('type', 'submit'); 191 if(!$pos) return; // no button -> source view mode 192 193 // do nothing if logged in user and no CAPTCHA required 194 if(!$this->getConf('forusers') && $_SERVER['REMOTE_USER']) { 195 return; 196 } 197 198 // get the CAPTCHA 199 /** @var helper_plugin_captcha $helper */ 200 $helper = plugin_load('helper', 'captcha'); 201 $out = $helper->getHTML(); 202 203 // new wiki - insert after the submit button 204 $event->data->insertElement($pos + 1, $out); 205 } 206 207 /** 208 * Clean cookies once per day 209 */ 210 public function handle_indexer(Doku_Event $event, $param) { 211 $lastrun = getCacheName('captcha', '.captcha'); 212 $last = @filemtime($lastrun); 213 if(time() - $last < 24 * 60 * 60) return; 214 215 /** @var helper_plugin_captcha $helper */ 216 $helper = plugin_load('helper', 'captcha'); 217 $helper->_cleanCaptchaCookies(); 218 219 $event->preventDefault(); 220 $event->stopPropagation(); 221 } 222} 223 224