1<?php
2
3/**
4 * DokuWiki Plugin autonumbering (Syntax Component)
5 *
6 * @description         :   This plugin allows the use of multiples counters
7 *                          with multiples levels, within the same page.
8 *
9 * @syntax (Base)       :   ~~#~~
10 *                              --> Where ~~#~~ will be replaced by a number,
11 *                                  auto incremented, and saved in a common
12 *                                  counter.
13 * @syntax (ID)         :   ~~#@COUNTERID~~
14 *                              --> Where COUNTERID is an alphanumeric
15 *                                  identificator, including unserscore,
16 *                                  and starting with a @. This allows
17 *                                  the use of multiple counters.
18 * @syntax (forced)     :   ~~#NUM~~
19 *                              --> Where NUM is a positive number that will
20 *                                  be the begining of the auto incrementation
21 *                                  from there.
22 * @syntax (multilevel) :   ~~#.#~~
23 *                              --> Where .# represent a sublevel and can be
24 *                                  repeated as much as needed.
25 *
26 * @syntax (text)       :   ~~REPORT.EXAMPLE.#~~
27 *                              --> Where only the third level will be an auto
28 *                                  incremented number. The first level will
29 *                                  be a repeated text. Here it will be REPORT.
30 *                                  Samething for the second level with EXAMPLE.
31 *                                  When using text in a level, it will be
32 *                                  implicitly used as counter ID if no counter
33 *                                  ID have been set with @COUNTERID.
34 *
35 * @example             :   ~~Test.#4.#6@CTR_ONE~~
36 *                              --> Where the number will have three levels.
37 *                                  First level will be the text « Test ».
38 *                                  Second level will be an auto incremented
39 *                                  number starting at 4. Third level will be
40 *                                  an auto incremented number starting at 6.
41 *                                  All this will be save in the counter
42 *                                  « CTR_ONE ».
43 *
44 * @license             :   GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
45 */
46
47// phpcs:disable PSR1.Files.SideEffects.FoundWithSymbols
48use dokuwiki\Extension\SyntaxPlugin;
49
50// must be run within DokuWiki
51if (!defined('DOKU_INC')) {
52    die();
53}
54// phpcs:enable
55
56class syntax_plugin_autonumbering extends SyntaxPlugin
57{
58    public $PLUGIN_PATTERN = "~~[a-zA-Z0-9_\.#@]*#[a-zA-Z0-9_\.#@]*~~";
59    public $NUMBER_PATTERN = "[0-9]+";
60    public $COUNTER_ID_PATTERN = "[a-zA-Z0-9_]+";
61
62    public function getType()
63    {
64        return 'substition';
65    }
66
67    public function getPType()
68    {
69        return 'normal';
70    }
71
72    public function getSort()
73    {
74        return 45;
75    }
76
77    public function connectTo($mode)
78    {
79        $this->Lexer->addSpecialPattern($this->PLUGIN_PATTERN, $mode, 'plugin_autonumbering');
80    }
81
82    public function handle($match, $state, $pos, Doku_Handler $handler)
83    {
84        global $COUNTER;
85        $counterID = '';
86        $class = 'autonumberingAll autonumbering';
87        switch ($state) {
88            case DOKU_LEXER_SPECIAL:
89                if (preg_match('/~~(.*?)~~/', $match, $matches)) {
90                    $data = $matches[1];
91
92                    if (!empty($data)) {
93                        // Search for EXPLICIT counter ID
94                        if (preg_match('/@(' . $this->COUNTER_ID_PATTERN . ')/', $data, $matches)) {
95                            $counterID = $matches[1];
96                            // Remove counter ID from $data
97                            $data = str_replace('@' . $counterID, '', $data);
98                        } else {    // Search for IMPLICIT counter ID
99                            $alpha = preg_replace('/[^a-zA-Z]/', '', $data);
100                            if (!empty($alpha)) {
101                                $counterID = $alpha;
102                            }
103                        }
104
105                        // Separate levels
106                        $dataTab = explode('.', $data);
107
108                        // Get levels quantity
109                        $levelsQty = count($dataTab);
110                        $currentLevel = $levelsQty - 1;
111
112                        // Check if parent level exist
113                        for ($i = 0; $i < $levelsQty; ++$i) {
114                            // Check if level contain text
115                            if (ctype_alpha($dataTab[$i])) {
116                                $COUNTER[$counterID][$i] = $dataTab[$i];
117                                $class .= '_' . $dataTab[$i];
118                            } elseif (preg_match('/(' . $this->NUMBER_PATTERN . ')/', $dataTab[$i], $matches)) {
119                                // Search for a forced value
120                                if ($i == $currentLevel) {
121                                    $COUNTER[$counterID][$i] = $matches[1] - 1;
122                                } else {
123                                    $COUNTER[$counterID][$i] = $matches[1];
124                                }
125                            } elseif ((!isset($COUNTER[$counterID][$i])) || ($COUNTER[$counterID][$i] == 0)) {
126                                // initialize if needed
127                                if ($i == $currentLevel) {
128                                    $COUNTER[$counterID][$i] = 0;
129                                } else {
130                                    $COUNTER[$counterID][$i] = 1;
131                                }
132                            }
133                        }
134
135                        // Check if child level exist, and initialize
136                        $counter_levelsQty = count($COUNTER[$counterID]);
137                        for ($i = $currentLevel + 1; $i < $counter_levelsQty; ++$i) {
138                            $COUNTER[$counterID][$i] = 0;
139                        }
140
141                        // Increment current level
142                        ++$COUNTER[$counterID][$currentLevel];
143
144                        // Return the number, according the level asked
145                        $number = "<span class=\"$class\">";
146                        $period = '';
147                        for ($i = 0; $i < $levelsQty; ++$i) {
148                            $number .= $period . $COUNTER[$counterID][$i];
149                            if (ctype_alpha($COUNTER[$counterID][$i])) {
150                                if ($period != '.') {
151                                    $period = ' ';
152                                }
153                            } else {
154                                $period = '.';
155                            }
156                        }
157                        $number .= '</span>';
158                        return array($number, null);
159                    } else {
160                        return array($match, null);
161                    }
162                }
163                break;
164        }
165        return array();
166    }
167
168    public function render($mode, Doku_Renderer $renderer, $data)
169    {
170        if (($mode == 'xhtml') && (!empty($data))) {
171            list($number, $null) = $data;
172            $renderer->doc .= $number;
173            return true;
174        }
175        return false;
176    }
177
178    // To work with the plugin « reproduce », who needs to do
179    // the numbering prior to reproducing the code.
180    public function doNumbering($pageContent)
181    {
182        $qtyOccurrences = preg_match_all('/~~(.*?)~~/', $pageContent, $matches);
183        if ($qtyOccurrences > 0) {
184            for ($i = 0; $i < $qtyOccurrences; $i++) {
185                list($number, $null) = $this->handle($matches[0][$i], DOKU_LEXER_SPECIAL, null, $handler);
186                $pageContent = preg_replace('(' . $matches[0][$i] . ')', $number, $pageContent, 1);
187            }
188        }
189        return $pageContent;
190    }
191}
192