xref: /plugin/captcha/action.php (revision 1c08a51c93133f56d523962518a3729c88bcf3c9)
142a27035SAndreas Gohr<?php
242a27035SAndreas Gohr/**
342a27035SAndreas Gohr * CAPTCHA antispam plugin
442a27035SAndreas Gohr *
542a27035SAndreas Gohr * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
642a27035SAndreas Gohr * @author     Andreas Gohr <gohr@cosmocode.de>
742a27035SAndreas Gohr */
8*1c08a51cSAndreas Gohrclass action_plugin_captcha extends DokuWiki_Action_Plugin
9*1c08a51cSAndreas Gohr{
1042a27035SAndreas Gohr
1142a27035SAndreas Gohr    /**
1242a27035SAndreas Gohr     * register the eventhandlers
1342a27035SAndreas Gohr     */
14*1c08a51cSAndreas Gohr    public function register(Doku_Event_Handler $controller)
15*1c08a51cSAndreas Gohr    {
167218f96cSAndreas Gohr        // check CAPTCHA success
17*1c08a51cSAndreas Gohr        $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_captcha_input', []);
1842a27035SAndreas Gohr
197218f96cSAndreas Gohr        // inject in edit form
20*1c08a51cSAndreas Gohr        $controller->register_hook('HTML_EDITFORM_OUTPUT', 'BEFORE', $this, 'handle_form_output', []); //old
21*1c08a51cSAndreas Gohr        $controller->register_hook('FORM_EDIT_OUTPUT', 'BEFORE', $this, 'handle_form_output', []); //new
2242a27035SAndreas Gohr
237218f96cSAndreas Gohr        // inject in user registration
24*1c08a51cSAndreas Gohr        $controller->register_hook('HTML_REGISTERFORM_OUTPUT', 'BEFORE', $this, 'handle_form_output', []); //old
25*1c08a51cSAndreas Gohr        $controller->register_hook('FORM_REGISTER_OUTPUT', 'BEFORE', $this, 'handle_form_output', []); //new
26f74276b8SAndreas Gohr
27f74276b8SAndreas Gohr        // inject in password reset
28*1c08a51cSAndreas Gohr        $controller->register_hook('HTML_RESENDPWDFORM_OUTPUT', 'BEFORE', $this, 'handle_form_output', []); //old
29*1c08a51cSAndreas Gohr        $controller->register_hook('FORM_RESENDPWD_OUTPUT', 'BEFORE', $this, 'handle_form_output', []); //new
30643f15bdSAndreas Gohr
31643f15bdSAndreas Gohr        if ($this->getConf('loginprotect')) {
32643f15bdSAndreas Gohr            // inject in login form
33*1c08a51cSAndreas Gohr            $controller->register_hook('HTML_LOGINFORM_OUTPUT', 'BEFORE', $this, 'handle_form_output', []); // old
34*1c08a51cSAndreas Gohr            $controller->register_hook('FORM_LOGIN_OUTPUT', 'BEFORE', $this, 'handle_form_output', []); // new
35*1c08a51cSAndreas Gohr
36643f15bdSAndreas Gohr            // check on login
37*1c08a51cSAndreas Gohr            $controller->register_hook('AUTH_LOGIN_CHECK', 'BEFORE', $this, 'handle_login', []);
38643f15bdSAndreas Gohr        }
39cde3ece1SAndreas Gohr
40cde3ece1SAndreas Gohr        // clean up captcha cookies
41*1c08a51cSAndreas Gohr        $controller->register_hook('INDEXER_TASKS_RUN', 'AFTER', $this, 'handle_indexer', []);
4242a27035SAndreas Gohr    }
4342a27035SAndreas Gohr
4442a27035SAndreas Gohr    /**
45bd26d35bSAndreas Gohr     * Check if the current mode should be handled by CAPTCHA
46bd26d35bSAndreas Gohr     *
47f74276b8SAndreas Gohr     * Note: checking needs to be done when a form has been submitted, not when the form
48f74276b8SAndreas Gohr     * is shown for the first time. Except for the editing process this is not determined
49f74276b8SAndreas Gohr     * by $act alone but needs to inspect other input variables.
50f74276b8SAndreas Gohr     *
51bd26d35bSAndreas Gohr     * @param string $act cleaned action mode
52bd26d35bSAndreas Gohr     * @return bool
53bd26d35bSAndreas Gohr     */
54*1c08a51cSAndreas Gohr    protected function needs_checking($act)
55*1c08a51cSAndreas Gohr    {
56bd26d35bSAndreas Gohr        global $INPUT;
57bd26d35bSAndreas Gohr
58bd26d35bSAndreas Gohr        switch ($act) {
59bd26d35bSAndreas Gohr            case 'save':
60bd26d35bSAndreas Gohr                return true;
61bd26d35bSAndreas Gohr            case 'register':
62f74276b8SAndreas Gohr            case 'resendpwd':
63bd26d35bSAndreas Gohr                return $INPUT->bool('save');
64643f15bdSAndreas Gohr            case 'login':
65643f15bdSAndreas Gohr                // we do not handle this here, but in handle_login()
66bd26d35bSAndreas Gohr            default:
67bd26d35bSAndreas Gohr                return false;
68bd26d35bSAndreas Gohr        }
69bd26d35bSAndreas Gohr    }
70bd26d35bSAndreas Gohr
71bd26d35bSAndreas Gohr    /**
72bd26d35bSAndreas Gohr     * Aborts the given mode
73bd26d35bSAndreas Gohr     *
74bd26d35bSAndreas Gohr     * Aborting depends on the mode. It might unset certain input parameters or simply switch
75bd26d35bSAndreas Gohr     * the mode to something else (giving as return which needs to be passed back to the
76bd26d35bSAndreas Gohr     * ACTION_ACT_PREPROCESS event)
77bd26d35bSAndreas Gohr     *
78bd26d35bSAndreas Gohr     * @param string $act cleaned action mode
79bd26d35bSAndreas Gohr     * @return string the new mode to use
80bd26d35bSAndreas Gohr     */
81*1c08a51cSAndreas Gohr    protected function abort_action($act)
82*1c08a51cSAndreas Gohr    {
83bd26d35bSAndreas Gohr        global $INPUT;
84bd26d35bSAndreas Gohr
85bd26d35bSAndreas Gohr        switch ($act) {
86bd26d35bSAndreas Gohr            case 'save':
87bd26d35bSAndreas Gohr                return 'preview';
88bd26d35bSAndreas Gohr            case 'register':
89f74276b8SAndreas Gohr            case 'resendpwd':
90bd26d35bSAndreas Gohr                $INPUT->post->set('save', false);
91f74276b8SAndreas Gohr                return $act;
92643f15bdSAndreas Gohr            case 'login':
93643f15bdSAndreas Gohr                // we do not handle this here, but in handle_login()
94bd26d35bSAndreas Gohr            default:
95bd26d35bSAndreas Gohr                return $act;
96bd26d35bSAndreas Gohr        }
97bd26d35bSAndreas Gohr    }
98bd26d35bSAndreas Gohr
99bd26d35bSAndreas Gohr    /**
100643f15bdSAndreas Gohr     * Handles CAPTCHA check in login
101643f15bdSAndreas Gohr     *
102643f15bdSAndreas Gohr     * Logins happen very early in the DokuWiki lifecycle, so we have to intercept them
103643f15bdSAndreas Gohr     * in their own event.
104643f15bdSAndreas Gohr     *
105643f15bdSAndreas Gohr     * @param Doku_Event $event
106643f15bdSAndreas Gohr     * @param $param
107643f15bdSAndreas Gohr     */
108*1c08a51cSAndreas Gohr    public function handle_login(Doku_Event $event, $param)
109*1c08a51cSAndreas Gohr    {
110643f15bdSAndreas Gohr        global $INPUT;
111643f15bdSAndreas Gohr        if (!$this->getConf('loginprotect')) return; // no protection wanted
112643f15bdSAndreas Gohr        if (!$INPUT->bool('u')) return; // this login was not triggered by a form
113643f15bdSAndreas Gohr
114643f15bdSAndreas Gohr        // we need to have $ID set for the captcha check
115643f15bdSAndreas Gohr        global $ID;
116643f15bdSAndreas Gohr        $ID = getID();
117643f15bdSAndreas Gohr
118643f15bdSAndreas Gohr        /** @var helper_plugin_captcha $helper */
119643f15bdSAndreas Gohr        $helper = plugin_load('helper', 'captcha');
120643f15bdSAndreas Gohr        if (!$helper->check()) {
121643f15bdSAndreas Gohr            $event->data['silent'] = true; // we have our own message
122643f15bdSAndreas Gohr            $event->result = false; // login fail
123643f15bdSAndreas Gohr            $event->preventDefault();
124643f15bdSAndreas Gohr            $event->stopPropagation();
125643f15bdSAndreas Gohr        }
126643f15bdSAndreas Gohr    }
127643f15bdSAndreas Gohr
128643f15bdSAndreas Gohr    /**
129643f15bdSAndreas Gohr     * Intercept all actions and check for CAPTCHA first.
13042a27035SAndreas Gohr     */
131*1c08a51cSAndreas Gohr    public function handle_captcha_input(Doku_Event $event, $param)
132*1c08a51cSAndreas Gohr    {
1337218f96cSAndreas Gohr        $act = act_clean($event->data);
134f74276b8SAndreas Gohr        if (!$this->needs_checking($act)) return;
13593f66506SAndreas Gohr
13642a27035SAndreas Gohr        // do nothing if logged in user and no CAPTCHA required
13742a27035SAndreas Gohr        if (!$this->getConf('forusers') && $_SERVER['REMOTE_USER']) {
13842a27035SAndreas Gohr            return;
13942a27035SAndreas Gohr        }
14042a27035SAndreas Gohr
14177e00bf9SAndreas Gohr        // check captcha
1427218f96cSAndreas Gohr        /** @var helper_plugin_captcha $helper */
14377e00bf9SAndreas Gohr        $helper = plugin_load('helper', 'captcha');
14477e00bf9SAndreas Gohr        if (!$helper->check()) {
145bd26d35bSAndreas Gohr            $event->data = $this->abort_action($act);
14642a27035SAndreas Gohr        }
14742a27035SAndreas Gohr    }
14842a27035SAndreas Gohr
14942a27035SAndreas Gohr    /**
150*1c08a51cSAndreas Gohr     * Inject the CAPTCHA in a DokuForm or \dokuwiki\Form\Form
15142a27035SAndreas Gohr     */
152*1c08a51cSAndreas Gohr    public function handle_form_output(Doku_Event $event, $param)
153*1c08a51cSAndreas Gohr    {
154*1c08a51cSAndreas Gohr        /** @var \dokuwiki\Form\Form|\Doku_Form $form */
155*1c08a51cSAndreas Gohr        $form = $event->data;
156*1c08a51cSAndreas Gohr
15747afabe6SAndreas Gohr        // get position of submit button
158*1c08a51cSAndreas Gohr        if (is_a($form, \dokuwiki\Form\Form::class)) {
159*1c08a51cSAndreas Gohr            $pos = $form->findPositionByAttribute('type', 'submit');
160*1c08a51cSAndreas Gohr        } else {
161*1c08a51cSAndreas Gohr            $pos = $form->findElementByAttribute('type', 'submit');
162*1c08a51cSAndreas Gohr        }
16347afabe6SAndreas Gohr        if (!$pos) return; // no button -> source view mode
16447afabe6SAndreas Gohr
16542a27035SAndreas Gohr        // do nothing if logged in user and no CAPTCHA required
16642a27035SAndreas Gohr        if (!$this->getConf('forusers') && $_SERVER['REMOTE_USER']) {
16742a27035SAndreas Gohr            return;
16842a27035SAndreas Gohr        }
16942a27035SAndreas Gohr
17077e00bf9SAndreas Gohr        // get the CAPTCHA
1717218f96cSAndreas Gohr        /** @var helper_plugin_captcha $helper */
17277e00bf9SAndreas Gohr        $helper = plugin_load('helper', 'captcha');
17377e00bf9SAndreas Gohr        $out = $helper->getHTML();
17447afabe6SAndreas Gohr
175*1c08a51cSAndreas Gohr        // insert before the submit button
176*1c08a51cSAndreas Gohr        if (is_a($form, \dokuwiki\Form\Form::class)) {
177*1c08a51cSAndreas Gohr            $form->addHTML($out, $pos);
178*1c08a51cSAndreas Gohr        } else {
179*1c08a51cSAndreas Gohr            $form->insertElement($pos, $out);
180*1c08a51cSAndreas Gohr        }
18142a27035SAndreas Gohr    }
18242a27035SAndreas Gohr
183cde3ece1SAndreas Gohr    /**
184cde3ece1SAndreas Gohr     * Clean cookies once per day
185cde3ece1SAndreas Gohr     */
186*1c08a51cSAndreas Gohr    public function handle_indexer(Doku_Event $event, $param)
187*1c08a51cSAndreas Gohr    {
188cde3ece1SAndreas Gohr        $lastrun = getCacheName('captcha', '.captcha');
189cde3ece1SAndreas Gohr        $last = @filemtime($lastrun);
190cde3ece1SAndreas Gohr        if (time() - $last < 24 * 60 * 60) return;
191cde3ece1SAndreas Gohr
192cde3ece1SAndreas Gohr        /** @var helper_plugin_captcha $helper */
193cde3ece1SAndreas Gohr        $helper = plugin_load('helper', 'captcha');
194cde3ece1SAndreas Gohr        $helper->_cleanCaptchaCookies();
1955d59bd09SAndreas Gohr        touch($lastrun);
196cde3ece1SAndreas Gohr
197cde3ece1SAndreas Gohr        $event->preventDefault();
198cde3ece1SAndreas Gohr        $event->stopPropagation();
199cde3ece1SAndreas Gohr    }
20042a27035SAndreas Gohr}
20142a27035SAndreas Gohr
202