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