xref: /plugin/captcha/action.php (revision cde3ece1b2b75d735e60463369185c6646617354)
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 */
842a27035SAndreas Gohr
942a27035SAndreas Gohr// must be run within Dokuwiki
1042a27035SAndreas Gohrif(!defined('DOKU_INC')) die();
1142a27035SAndreas Gohrif(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/');
127218f96cSAndreas Gohr
1342a27035SAndreas Gohrclass action_plugin_captcha extends DokuWiki_Action_Plugin {
1442a27035SAndreas Gohr
1542a27035SAndreas Gohr    /**
1642a27035SAndreas Gohr     * register the eventhandlers
1742a27035SAndreas Gohr     */
187218f96cSAndreas Gohr    public function register(Doku_Event_Handler $controller) {
197218f96cSAndreas Gohr        // check CAPTCHA success
20c2695b40SAndreas Gohr        $controller->register_hook(
21c2695b40SAndreas Gohr            'ACTION_ACT_PREPROCESS',
2242a27035SAndreas Gohr            'BEFORE',
2342a27035SAndreas Gohr            $this,
247218f96cSAndreas Gohr            'handle_captcha_input',
25c2695b40SAndreas Gohr            array()
26c2695b40SAndreas Gohr        );
2742a27035SAndreas Gohr
287218f96cSAndreas Gohr        // inject in edit form
29c2695b40SAndreas Gohr        $controller->register_hook(
30c2695b40SAndreas Gohr            'HTML_EDITFORM_OUTPUT',
3147afabe6SAndreas Gohr            'BEFORE',
3247afabe6SAndreas Gohr            $this,
337218f96cSAndreas Gohr            'handle_form_output',
347218f96cSAndreas Gohr            array()
35c2695b40SAndreas Gohr        );
3642a27035SAndreas Gohr
377218f96cSAndreas Gohr        // inject in user registration
38c2695b40SAndreas Gohr        $controller->register_hook(
39c2695b40SAndreas Gohr            'HTML_REGISTERFORM_OUTPUT',
4047afabe6SAndreas Gohr            'BEFORE',
4147afabe6SAndreas Gohr            $this,
427218f96cSAndreas Gohr            'handle_form_output',
437218f96cSAndreas Gohr            array()
44c2695b40SAndreas Gohr        );
45f74276b8SAndreas Gohr
46f74276b8SAndreas Gohr        // inject in password reset
47f74276b8SAndreas Gohr        $controller->register_hook(
48f74276b8SAndreas Gohr            'HTML_RESENDPWDFORM_OUTPUT',
49f74276b8SAndreas Gohr            'BEFORE',
50f74276b8SAndreas Gohr            $this,
51f74276b8SAndreas Gohr            'handle_form_output',
52f74276b8SAndreas Gohr            array()
53f74276b8SAndreas Gohr        );
54643f15bdSAndreas Gohr
55643f15bdSAndreas Gohr        if($this->getConf('loginprotect')) {
56643f15bdSAndreas Gohr            // inject in login form
57643f15bdSAndreas Gohr            $controller->register_hook(
58643f15bdSAndreas Gohr                'HTML_LOGINFORM_OUTPUT',
59643f15bdSAndreas Gohr                'BEFORE',
60643f15bdSAndreas Gohr                $this,
61643f15bdSAndreas Gohr                'handle_form_output',
62643f15bdSAndreas Gohr                array()
63643f15bdSAndreas Gohr            );
64643f15bdSAndreas Gohr            // check on login
65643f15bdSAndreas Gohr            $controller->register_hook(
66643f15bdSAndreas Gohr                'AUTH_LOGIN_CHECK',
67643f15bdSAndreas Gohr                'BEFORE',
68643f15bdSAndreas Gohr                $this,
69643f15bdSAndreas Gohr                'handle_login',
70643f15bdSAndreas Gohr                array()
71643f15bdSAndreas Gohr            );
72643f15bdSAndreas Gohr        }
73*cde3ece1SAndreas Gohr
74*cde3ece1SAndreas Gohr        // clean up captcha cookies
75*cde3ece1SAndreas Gohr        $controller->register_hook(
76*cde3ece1SAndreas Gohr            'INDEXER_TASKS_RUN',
77*cde3ece1SAndreas Gohr            'AFTER',
78*cde3ece1SAndreas Gohr            $this,
79*cde3ece1SAndreas Gohr            'handle_indexer',
80*cde3ece1SAndreas Gohr            array()
81*cde3ece1SAndreas Gohr        );
8242a27035SAndreas Gohr    }
8342a27035SAndreas Gohr
8442a27035SAndreas Gohr    /**
85bd26d35bSAndreas Gohr     * Check if the current mode should be handled by CAPTCHA
86bd26d35bSAndreas Gohr     *
87f74276b8SAndreas Gohr     * Note: checking needs to be done when a form has been submitted, not when the form
88f74276b8SAndreas Gohr     * is shown for the first time. Except for the editing process this is not determined
89f74276b8SAndreas Gohr     * by $act alone but needs to inspect other input variables.
90f74276b8SAndreas Gohr     *
91bd26d35bSAndreas Gohr     * @param string $act cleaned action mode
92bd26d35bSAndreas Gohr     * @return bool
93bd26d35bSAndreas Gohr     */
94f74276b8SAndreas Gohr    protected function needs_checking($act) {
95bd26d35bSAndreas Gohr        global $INPUT;
96bd26d35bSAndreas Gohr
97bd26d35bSAndreas Gohr        switch($act) {
98bd26d35bSAndreas Gohr            case 'save':
99bd26d35bSAndreas Gohr                return true;
100bd26d35bSAndreas Gohr            case 'register':
101f74276b8SAndreas Gohr            case 'resendpwd':
102bd26d35bSAndreas Gohr                return $INPUT->bool('save');
103643f15bdSAndreas Gohr            case 'login':
104643f15bdSAndreas Gohr                // we do not handle this here, but in handle_login()
105bd26d35bSAndreas Gohr            default:
106bd26d35bSAndreas Gohr                return false;
107bd26d35bSAndreas Gohr        }
108bd26d35bSAndreas Gohr    }
109bd26d35bSAndreas Gohr
110bd26d35bSAndreas Gohr    /**
111bd26d35bSAndreas Gohr     * Aborts the given mode
112bd26d35bSAndreas Gohr     *
113bd26d35bSAndreas Gohr     * Aborting depends on the mode. It might unset certain input parameters or simply switch
114bd26d35bSAndreas Gohr     * the mode to something else (giving as return which needs to be passed back to the
115bd26d35bSAndreas Gohr     * ACTION_ACT_PREPROCESS event)
116bd26d35bSAndreas Gohr     *
117bd26d35bSAndreas Gohr     * @param string $act cleaned action mode
118bd26d35bSAndreas Gohr     * @return string the new mode to use
119bd26d35bSAndreas Gohr     */
120bd26d35bSAndreas Gohr    protected function abort_action($act) {
121bd26d35bSAndreas Gohr        global $INPUT;
122bd26d35bSAndreas Gohr
123bd26d35bSAndreas Gohr        switch($act) {
124bd26d35bSAndreas Gohr            case 'save':
125bd26d35bSAndreas Gohr                return 'preview';
126bd26d35bSAndreas Gohr            case 'register':
127f74276b8SAndreas Gohr            case 'resendpwd':
128bd26d35bSAndreas Gohr                $INPUT->post->set('save', false);
129f74276b8SAndreas Gohr                return $act;
130643f15bdSAndreas Gohr            case 'login':
131643f15bdSAndreas Gohr                // we do not handle this here, but in handle_login()
132bd26d35bSAndreas Gohr            default:
133bd26d35bSAndreas Gohr                return $act;
134bd26d35bSAndreas Gohr        }
135bd26d35bSAndreas Gohr    }
136bd26d35bSAndreas Gohr
137bd26d35bSAndreas Gohr    /**
138643f15bdSAndreas Gohr     * Handles CAPTCHA check in login
139643f15bdSAndreas Gohr     *
140643f15bdSAndreas Gohr     * Logins happen very early in the DokuWiki lifecycle, so we have to intercept them
141643f15bdSAndreas Gohr     * in their own event.
142643f15bdSAndreas Gohr     *
143643f15bdSAndreas Gohr     * @param Doku_Event $event
144643f15bdSAndreas Gohr     * @param $param
145643f15bdSAndreas Gohr     */
146643f15bdSAndreas Gohr    public function handle_login(Doku_Event $event, $param) {
147643f15bdSAndreas Gohr        global $INPUT;
148643f15bdSAndreas Gohr        if(!$this->getConf('loginprotect')) return; // no protection wanted
149643f15bdSAndreas Gohr        if(!$INPUT->bool('u')) return; // this login was not triggered by a form
150643f15bdSAndreas Gohr
151643f15bdSAndreas Gohr        // we need to have $ID set for the captcha check
152643f15bdSAndreas Gohr        global $ID;
153643f15bdSAndreas Gohr        $ID = getID();
154643f15bdSAndreas Gohr
155643f15bdSAndreas Gohr        /** @var helper_plugin_captcha $helper */
156643f15bdSAndreas Gohr        $helper = plugin_load('helper', 'captcha');
157643f15bdSAndreas Gohr        if(!$helper->check()) {
158643f15bdSAndreas Gohr            $event->data['silent'] = true; // we have our own message
159643f15bdSAndreas Gohr            $event->result = false; // login fail
160643f15bdSAndreas Gohr            $event->preventDefault();
161643f15bdSAndreas Gohr            $event->stopPropagation();
162643f15bdSAndreas Gohr        }
163643f15bdSAndreas Gohr    }
164643f15bdSAndreas Gohr
165643f15bdSAndreas Gohr    /**
166643f15bdSAndreas Gohr     * Intercept all actions and check for CAPTCHA first.
16742a27035SAndreas Gohr     */
1687218f96cSAndreas Gohr    public function handle_captcha_input(Doku_Event $event, $param) {
1697218f96cSAndreas Gohr        $act = act_clean($event->data);
170f74276b8SAndreas Gohr        if(!$this->needs_checking($act)) return;
17193f66506SAndreas Gohr
17242a27035SAndreas Gohr        // do nothing if logged in user and no CAPTCHA required
17342a27035SAndreas Gohr        if(!$this->getConf('forusers') && $_SERVER['REMOTE_USER']) {
17442a27035SAndreas Gohr            return;
17542a27035SAndreas Gohr        }
17642a27035SAndreas Gohr
17777e00bf9SAndreas Gohr        // check captcha
1787218f96cSAndreas Gohr        /** @var helper_plugin_captcha $helper */
17977e00bf9SAndreas Gohr        $helper = plugin_load('helper', 'captcha');
18077e00bf9SAndreas Gohr        if(!$helper->check()) {
181bd26d35bSAndreas Gohr            $event->data = $this->abort_action($act);
18242a27035SAndreas Gohr        }
18342a27035SAndreas Gohr    }
18442a27035SAndreas Gohr
18542a27035SAndreas Gohr    /**
1867218f96cSAndreas Gohr     * Inject the CAPTCHA in a DokuForm
18742a27035SAndreas Gohr     */
1887218f96cSAndreas Gohr    public function handle_form_output(Doku_Event $event, $param) {
18947afabe6SAndreas Gohr        // get position of submit button
19047afabe6SAndreas Gohr        $pos = $event->data->findElementByAttribute('type', 'submit');
19147afabe6SAndreas Gohr        if(!$pos) return; // no button -> source view mode
19247afabe6SAndreas Gohr
19342a27035SAndreas Gohr        // do nothing if logged in user and no CAPTCHA required
19442a27035SAndreas Gohr        if(!$this->getConf('forusers') && $_SERVER['REMOTE_USER']) {
19542a27035SAndreas Gohr            return;
19642a27035SAndreas Gohr        }
19742a27035SAndreas Gohr
19877e00bf9SAndreas Gohr        // get the CAPTCHA
1997218f96cSAndreas Gohr        /** @var helper_plugin_captcha $helper */
20077e00bf9SAndreas Gohr        $helper = plugin_load('helper', 'captcha');
20177e00bf9SAndreas Gohr        $out = $helper->getHTML();
20247afabe6SAndreas Gohr
2037218f96cSAndreas Gohr        // new wiki - insert after the submit button
2047218f96cSAndreas Gohr        $event->data->insertElement($pos + 1, $out);
20542a27035SAndreas Gohr    }
20642a27035SAndreas Gohr
207*cde3ece1SAndreas Gohr    /**
208*cde3ece1SAndreas Gohr     * Clean cookies once per day
209*cde3ece1SAndreas Gohr     */
210*cde3ece1SAndreas Gohr    public function handle_indexer(Doku_Event $event, $param) {
211*cde3ece1SAndreas Gohr        $lastrun = getCacheName('captcha', '.captcha');
212*cde3ece1SAndreas Gohr        $last = @filemtime($lastrun);
213*cde3ece1SAndreas Gohr        if(time() - $last < 24 * 60 * 60) return;
214*cde3ece1SAndreas Gohr
215*cde3ece1SAndreas Gohr        /** @var helper_plugin_captcha $helper */
216*cde3ece1SAndreas Gohr        $helper = plugin_load('helper', 'captcha');
217*cde3ece1SAndreas Gohr        $helper->_cleanCaptchaCookies();
218*cde3ece1SAndreas Gohr
219*cde3ece1SAndreas Gohr        $event->preventDefault();
220*cde3ece1SAndreas Gohr        $event->stopPropagation();
221*cde3ece1SAndreas Gohr    }
22242a27035SAndreas Gohr}
22342a27035SAndreas Gohr
224