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