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