1<?php
2/**
3 * DokuWiki Plugin flashcards (Syntax Component)
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * author: Jakub Pelc
7 */
8
9// Must be run within DokuWiki
10if (!defined('DOKU_INC')) die();
11
12class syntax_plugin_flashcards extends DokuWiki_Syntax_Plugin {
13
14    public function getType() {
15        return 'container';
16    }
17
18    public function getPType() {
19        return 'block';
20    }
21
22    public function getAllowedTypes() {
23        return ['container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs'];
24    }
25
26    public function getSort() {
27        return 158;
28    }
29
30    public function connectTo($mode) {
31        $this->Lexer->addSpecialPattern('<flashcards.*?>.*?</flashcards>', $mode, 'plugin_flashcards');
32    }
33
34    public function handle($match, $state, $pos, Doku_Handler $handler) {
35        $match = trim(substr($match, 12, -13)); // Strip <flashcards> tags
36
37        // Parse optional attributes
38        preg_match('/heading="(.*?)"/', $match, $headingMatch);
39        preg_match('/subtext="(.*?)"/', $match, $subtextMatch);
40        preg_match('/skiptext="(.*?)"/', $match, $skipTextMatch);
41        preg_match('/nexttext="(.*?)"/', $match, $nextTextMatch);
42        preg_match('/defaultnum="(\d+)"/', $match, $defaultNumMatch);
43
44        $heading = $headingMatch[1] ?? 'Flashcard Quiz';
45        $subtext = $subtextMatch[1] ?? 'Answer the following questions:';
46        $skipText = $skipTextMatch[1] ?? 'Skip';
47        $nextText = $nextTextMatch[1] ?? 'Next';
48        $defaultNum = $defaultNumMatch[1] ?? 5; // Default to 5 if not provided
49
50        // Parse the content within the <questions></questions> block
51        if (preg_match('/<questions>(.*?)<\/questions>/s', $match, $contentMatch)) {
52            $content = trim($contentMatch[1]);
53        } else {
54            return [
55                'heading' => $heading,
56                'subtext' => $subtext,
57                'skipText' => $skipText,
58                'nextText' => $nextText,
59                'defaultNum' => $defaultNum,
60                'questions' => [], // No valid content found
61            ];
62        }
63
64        // Parse questions and answers using --- as delimiter
65        $questions = [];
66        foreach (preg_split('/---\n/', $content) as $block) {
67            $lines = array_filter(explode("\n", trim($block))); // Filter empty lines
68            $question = array_shift($lines);
69
70            if (empty($question)) {
71                continue; // Skip empty question blocks
72            }
73
74            $answers = [];
75            $correctAnswerIndex = null;
76
77            foreach ($lines as $index => $line) {
78                $line = trim($line);
79
80                if (strpos($line, '*') !== false) {
81                    $correctAnswerIndex = $index;
82                    $line = str_replace('*', '', $line); // Remove the correct marker
83                }
84
85                $answers[] = trim($line, "- ");
86            }
87
88            if (empty($answers) || $correctAnswerIndex === null) {
89                continue; // Skip questions without valid answers or no correct answer
90            }
91
92            $questions[] = [
93                'question' => trim($question),
94                'answers' => $answers,
95                'correct' => $correctAnswerIndex,
96            ];
97        }
98
99        return [
100            'heading' => $heading,
101            'subtext' => $subtext,
102            'skipText' => $skipText,
103            'nextText' => $nextText,
104            'defaultNum' => $defaultNum,
105            'questions' => $questions,
106        ];
107    }
108
109    public function render($mode, Doku_Renderer $renderer, $data) {
110        if ($mode !== 'xhtml') return false;
111
112        $heading = htmlspecialchars($data['heading']);
113        $subtext = htmlspecialchars($data['subtext']);
114        $skipText = htmlspecialchars($data['skipText']);
115        $nextText = htmlspecialchars($data['nextText']);
116        $defaultNum = htmlspecialchars($data['defaultNum']);
117        $questions = json_encode($data['questions'], JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
118
119        $renderer->doc .= "<div id='flashcards-container'>
120            <h1>{$heading}</h1>
121            <p>{$subtext}</p>
122            <div id='app-container'>
123                <div id='welcome-screen'>
124                    <input type='number' id='question-count-input' placeholder='Enter number of questions' min='1' value='{$defaultNum}'>
125                    <button class='button' id='start-button'>Start Test</button>
126                </div>
127                <div id='test-container' style='display: none;'>
128                    <div class='question'>
129                        <p id='question-text'></p>
130                        <div class='answers' id='answers-container'></div>
131                        <button id='skip-button' class='button'>{$skipText}</button>
132                        <button id='next-button' class='button' style='display: none;'>{$nextText}</button>
133                    </div>
134                </div>
135                <div id='summary-container' style='display: none;'>
136					<div id='detailed-summary'></div>
137				</div>
138            </div>
139        </div>
140        <link rel='stylesheet' href='" . DOKU_BASE . "lib/plugins/flashcards/style.css'>
141        <script>
142            const originalQuestions = {$questions};
143        </script>
144        <script src='" . DOKU_BASE . "lib/plugins/flashcards/script.js'></script>";
145
146        return true;
147    }
148}
149