<?php
if (!defined('DOKU_INC')) die();

/**
 * PASSWD Plugin: Handles AJAX captcha refresh and password verification.
 *
 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
 * @author tomLin1998567
 */
class action_plugin_passwd extends DokuWiki_Action_Plugin
{
    public function register(Doku_Event_Handler $controller)
    {
        $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handleAjax');
    }

    public function handleAjax(&$event, $param)
    {
        if ($event->data == 'passwd_refresh_captcha') {
            $event->preventDefault();
            $this->sendCaptcha();
            return;
        }

        if ($event->data != 'passwd_check') return;
        $event->preventDefault();

        global $INPUT, $ID;
        $block  = (int) $INPUT->str('block');
        $passwd = $INPUT->str('passwd');
        $id     = $INPUT->str('id', $ID);

        $captcha = $this->loadHelper('captcha', false);
        if ($this->isCaptchaRequired($captcha) && !$this->verifyCaptcha($captcha)) {
            return;
        }

        $blockText = $this->findPasswdBlock($id, $block);
        if ($blockText === false) return;

        $expected = $this->extractPassword($blockText);
        if ($expected === false) return;

        if ($passwd !== $expected) {
            $newCaptcha = $this->isCaptchaRequired($captcha) ? $captcha->getHTML() : '';
            echo 'WRONG_PASSWORD|' . $newCaptcha;
            return;
        }

        $this->renderContent($blockText);
    }

    /**
     * Check whether captcha verification is enabled and available.
     */
    protected function isCaptchaRequired($captcha)
    {
        return $this->getConf('require_captcha') && $captcha && $captcha->isEnabled();
    }

    /**
     * Verify the captcha. On failure output error with a fresh captcha.
     *
     * @return bool true if captcha passes (or not required)
     */
    protected function verifyCaptcha($captcha)
    {
        if (!$captcha->check()) {
            echo 'CAPTCHA_FAILED|' . $captcha->getHTML();
            return false;
        }
        return true;
    }

    /**
     * Read a wiki page and locate the Nth PASSWD block.
     *
     * @param string $id    Page ID
     * @param int    $block Zero-based block index
     * @return string|false The raw block text, or false on failure
     */
    protected function findPasswdBlock($id, $block)
    {
        preg_match_all(
            '/<passwd\b[^>]*?>[\s\S]*?<\/passwd>/',
            rawWiki($id), $matches, PREG_SET_ORDER
        );

        if (!isset($matches[$block])) {
            echo 'BLOCK_NOT_FOUND';
            return false;
        }
        return $matches[$block][0];
    }

    /**
     * Extract the password attribute from a PASSWD block.
     *
     * @param string $blockText Raw block text
     * @return string|false The password, or false if missing
     */
    protected function extractPassword($blockText)
    {
        if (!preg_match('/password="([^"]+)"/i', $blockText, $p)) {
            echo 'PASSWORD_NOT_FOUND';
            return false;
        }
        return $p[1];
    }

    /**
     * Parse and render the inner wikitext of a PASSWD block as XHTML.
     */
    protected function renderContent($blockText)
    {
        preg_match('/^<passwd\b[^>]*?>([\s\S]*?)<\/passwd>$/s', $blockText, $m);
        $instructions = p_get_instructions($m[1]);
        echo p_render('xhtml', $instructions, $info);
    }

    /**
     * Generate and return a fresh captcha HTML snippet.
     */
    protected function sendCaptcha()
    {
        $captcha = $this->loadHelper('captcha', false);
        if ($captcha && $captcha->isEnabled()) {
            echo $captcha->getHTML();
        }
    }
}
