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