xref: /plugin/captcha/action.php (revision 186227361d0efe9f0e20bd52c87dfbf939efb53a)
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