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