1<?php 2 3/** 4 * Plugin QnA: Block syntax 5 * 6 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 7 * @author Mykola Ostrovskyy <dwpforge@gmail.com> 8 */ 9 10class syntax_plugin_qna_toc extends DokuWiki_Syntax_Plugin { 11 12 private $mode; 13 14 /** 15 * Construct 16 */ 17 public function __construct() { 18 $this->mode = substr(get_class($this), 7); 19 } 20 21 /** 22 * What kind of syntax are we? 23 */ 24 public function getType() { 25 return 'container'; 26 } 27 28 public function getPType() { 29 return 'block'; 30 } 31 32 /** 33 * Where to sort in? 34 */ 35 public function getSort() { 36 return 55; 37 } 38 39 /** 40 * 41 */ 42 public function connectTo($mode) { 43 $this->Lexer->addSpecialPattern('~~(?:QNA|FAQ)(?:\s[^\n]*?)?~~', $mode, $this->mode); 44 } 45 46 /** 47 * 48 */ 49 public function handle($match, $state, $pos, Doku_Handler $handler) { 50 global $ID; 51 52 if ($state == DOKU_LEXER_SPECIAL) { 53 preg_match('/~~(?:QNA|FAQ)(.*?)~~/', $match, $match); 54 $data = preg_split('/\s+/', $match[1], -1, PREG_SPLIT_NO_EMPTY); 55 $resolver = new dokuwiki\File\PageResolver($ID); 56 57 foreach ($data as &$pageId) { 58 $pageId = $resolver->resolveId($pageId); 59 } 60 } 61 else { 62 $data = false; 63 } 64 65 return $data; 66 } 67 68 /** 69 * 70 */ 71 public function render($mode, Doku_Renderer $renderer, $data) { 72 global $ID; 73 74 if ($mode == 'xhtml') { 75 if (empty($data)) { 76 /* If no page is specified, render the questions from the current one */ 77 $data[0] = $ID; 78 } 79 80 $toc = $this->buildToc($data); 81 82 if (!empty($toc)) { 83 $this->compressToc($toc); 84 $this->normalizeToc($toc); 85 $this->renderToc($renderer, $toc); 86 } 87 88 return true; 89 } 90 elseif ($mode == 'metadata') { 91 if (!empty($data)) { 92 $this->addCacheDependencies($renderer, $data); 93 } 94 95 return true; 96 } 97 98 return false; 99 } 100 101 /** 102 * Assemble questions from all pages into a single TOC 103 */ 104 private function buildToc($pageId) { 105 $toc = array(); 106 107 foreach ($pageId as $id) { 108 if (auth_quickaclcheck($id) < AUTH_READ) { 109 continue; 110 } 111 112 $pageToc = p_get_metadata($id, 'description tableofquestions'); 113 114 if (!empty($pageToc)) { 115 foreach ($pageToc as $item) { 116 $item['link'] = $id . '#' . $item['id']; 117 $toc[] = $item; 118 } 119 } 120 } 121 122 return $toc; 123 } 124 125 /** 126 * Remove not used list levels 127 */ 128 private function compressToc(&$toc) { 129 $maxLevel = 0; 130 131 foreach ($toc as $item) { 132 if ($maxLevel < $item['level']) { 133 $maxLevel = $item['level']; 134 } 135 } 136 137 /* Build list level usage histogram */ 138 $level = array_fill(1, $maxLevel, 0); 139 140 foreach ($toc as $item) { 141 $level[$item['level']]++; 142 } 143 144 /* Determine how many unused list levels have to be skipped for each used one */ 145 $skipCount = 0; 146 147 for ($l = 1; $l <= $maxLevel; $l++) { 148 if ($level[$l] == 0) { 149 $skipCount++; 150 } 151 else { 152 $level[$l] = $skipCount; 153 } 154 } 155 156 /* Remove unused list levels */ 157 foreach ($toc as &$item) { 158 $item['level'] -= $level[$item['level']]; 159 } 160 } 161 162 /** 163 * Make sure that list starts with a first level item 164 */ 165 private function normalizeToc(&$toc) { 166 $offset = 9; 167 168 for ($i = 0; $toc[$i]['level'] > 1; $i++) { 169 if (($toc[$i]['level'] - $offset) < 1) { 170 $offset = $toc[$i]['level'] - 1; 171 } 172 173 $toc[$i]['level'] -= $offset; 174 } 175 } 176 177 /** 178 * 179 */ 180 private function renderToc($renderer, $toc) { 181 $renderer->doc .= '<div class="qna-toc">' . DOKU_LF; 182 $this->renderList($renderer, $toc, 0); 183 $renderer->doc .= '</div>' . DOKU_LF; 184 } 185 186 /** 187 * 188 */ 189 private function renderList($renderer, $toc, $index) { 190 $items = count( $toc ); 191 $level = $toc[$index]['level']; 192 193 $renderer->listu_open(); 194 195 for ($i = $index; ($i < $items) && ($toc[$i]['level'] == $level); $i++) { 196 $renderer->listitem_open($level); 197 $renderer->listcontent_open(); 198 $renderer->doc .= '<span class="qna-toc-' . $toc[$i]['class'] . '">'; 199 $renderer->internallink($toc[$i]['link'], $toc[$i]['title']); 200 $renderer->doc .= '</span>'; 201 $renderer->listcontent_close(); 202 203 if ((($i + 1) < $items) && ($toc[$i + 1]['level'] > $level)) { 204 $i = $this->renderList($renderer, $toc, $i + 1); 205 } 206 207 $renderer->listitem_close(); 208 } 209 210 $renderer->listu_close(); 211 212 return $i - 1; 213 } 214 215 /** 216 * 217 */ 218 private function addCacheDependencies($renderer, $pageId) { 219 foreach ($pageId as $id) { 220 $metafile = metaFN($id, '.meta'); 221 222 $renderer->meta['relation']['depends']['rendering'][$metafile] = true; 223 } 224 } 225} 226