1<?php 2 3/** 4 * NavBox Plugin for DokuWiki (Syntax Component) 5 * 6 * This plugin enables the ability to have a 'navbox' of related articles 7 * similar to the way Wikipedia does on some pages. 8 * 9 * Wikipedia Example: https://en.wikipedia.org/wiki/Singapore 10 * Scroll to the bottom to see "Singapore Articles" section 11 * 12 * @license GPL 2 https://www.gnu.org/licenses/gpl-2.0.html 13 * @author Jovin Sveinbjornsson 14 * @author Midgard Apps <hello@midgardapps.com> 15 */ 16 17// Must be run within DokuWiki 18if (!defined('DOKU_INC')) die(); 19 20class syntax_plugin_navbox extends DokuWiki_Syntax_Plugin { 21 22 /** 23 * What kind of syntax? 24 */ 25 public function getType() { 26 return 'container'; 27 } 28 29 /** 30 * How do we handle paragraphs? 31 */ 32 public function getPType() { 33 return 'block'; 34 } 35 36 /* 37 * When should this be executed? 38 */ 39 public function getSort() { 40 return 205; 41 } 42 43 public function getAllowedTypes() { 44 return array('container', 'formatting', 'substition', 'disabled', 'protected', 'paragraphs'); 45 } 46 47 /** 48 * Connect Lookup pattern to lexer 49 * 50 * @param string $mode Parser mode 51 */ 52 public function connectTo($mode) { 53 $this->Lexer->addSpecialPattern('<navbox>.*?</navbox>', $mode, 'plugin_navbox'); 54 } 55 56 /** 57 * Handler to match the data and kick off rendering 58 * 59 * @param string $match The text matched by the patterns 60 * @param int $state The lexer state for the match 61 * @param int $pos The character position of the matched text 62 * @param Doku_Handler $handler The Doku_Handler object 63 * 64 * @return array Data for the renderer 65 */ 66 public function handle($match, $state, $pos, Doku_Handler $handler) { 67 // Remove the <naxbox> and </navbox> 68 $match = substr($match, 8, -9); 69 // Separate the content into individual lines 70 $lines = explode("\n", $match); 71 // We'll store all our variables in here for processing later 72 $navbox = array(); 73 // Switches 74 $groupType = 0; // 0 = none, 1 = group, 2 = subgroup 75 $autoSub = false; 76 // Temporary Variables 77 $currentGroup = array(); 78 $current = ''; 79 $currentSub = ''; 80 81 82 // Loop over while we continue to have more to process 83 while(count($lines) > 0) { 84 // Clean up and work only with the current line, remove it from the remaining array 85 $line = trim(array_shift($lines)); 86 // If it's not valid, skip 87 if (strlen($line) < 1) continue; 88 89 // This if/else cascade proceeds in Specific -> Less Specific for syntax 90 if (strpos($line, '### !') !== false) { 91 // Subgroup with Advanced Syntax 92 // Turn on the 'subgroup' flag 93 $autoSub = true; 94 } else if (strpos($line, '### ') !== false) { 95 // Subgroup 96 // Name our Subgroup 97 $currentSub = substr($line, 4); 98 // Set the group type so we an add links appropriately 99 $groupType = 2; 100 // No further processing required 101 continue; 102 } else if (strpos($line, '## ') !== false) { 103 // Group 104 // Check if we already have a group, if so, do this 105 if (!empty($currentGroup)) { 106 // Store the current group 107 $navbox[$current] = $currentGroup; 108 // Start a new group 109 $currentGroup = array(); 110 // Clear the Subgroup name too 111 $currentSub = ''; 112 } 113 // Name our new group 114 $current = substr($line, 3); 115 // Set the group type so we can add links appropriately 116 $groupType = 1; 117 // No further processing required 118 continue; 119 } else if (strpos($line, '# ') !== false) { 120 // Title 121 // Store the title 122 $navbox['title'] = substr($line, 2); 123 // No further processing required 124 continue; 125 } else if (substr($line, 0, 2) == '[[') { 126 // We have a list of links 127 // These are the valid separators for the links, also no separators are valid too 128 $separators = [',', ';']; 129 // If we are dealign with a Group 130 if ($groupType == 1) { 131 // Store the links in the 'default' section 132 $currentGroup['default'] = str_replace($separators, '', $line); 133 } else if ($groupType == 2) { 134 // We are dealing with a Subgroup instead 135 // Store the links in the current Subgroup 136 $currentGroup[$currentSub] = str_replace($separators, '', $line); 137 } 138 } else { 139 // This is a automated flag, unset all switches 140 $autoSub = false; 141 $groupType = 0; 142 } 143 144 // The below will identify what kind of automated generation is required 145 if (strpos($line, '!ns') !== false) { 146 // A Namespace listing 147 // Offset if auto space is used 148 $offset = 0; 149 if ($autoSub) { 150 $offset = 4; 151 } 152 // Get the current namespace 153 $namespace = pageinfo()['namespace']; 154 // If the +n parameter is used, change the namspace 155 if (strpos($line, '+n') !== false) { 156 // Custom Namespace 157 $namespace = substr($line, 8 + $offset, -2); 158 } 159 // Get the lowest level namespace, this is our automatic title 160 $title = array_pop(explode(':', $namespace)); 161 // If the +t parameter is used, change the title 162 if (strpos($line, '+t') !== false) { 163 // Custom Title 164 $title = substr($line, 6 + $offset); 165 } 166 // If the +nt parameter is used, change the namespace and title 167 if (strpos($line, '+nt') !== false) { 168 // Find where the namespace begins 169 $nsStart = strpos($line, '[[') + 2; 170 // Find where the title begins 171 $tStart = strpos($line, '|') + 1; 172 // Extract the title 173 $title = substr($line, $tStart, -2); 174 // Extract the namespace 175 $namespace = substr($line, $nsStart, ($tStart - $nsStart - 1)); 176 } 177 // String for the working directory of the namespace 178 $dir = './data/pages/'.str_replace(':', '/', $namespace); 179 // Instantiate our Links variable 180 $links = ''; 181 // Look in the directory and get all .txt files (doku pages) 182 foreach (glob($dir.'/*.txt') as $filename) { 183 // Store each file as a new markup link 184 $links .= '[['.str_replace('/', ':', substr($filename, 13, -4)).']]'; 185 } 186 // Identify if this should be a subgroup 187 if ($autoSub) { 188 // Append to the parent group 189 $currentGroup[$title] = $links; 190 } else { 191 // Add as a main level group 192 $navbox[$title]['default'] = $links; 193 } 194 } else if (strpos($line, '!tree') !== false) { 195 // The hierarchy of this page 196 } else if (strpos($line, '!tag') !== false) { 197 // Tag listing, need to use the pagelist plugin for this one 198 // This is a stretch goal, well and truly 199 } 200 201 // We are working on the last line group, store our groups 202 if (count($lines) == 1) { 203 if (!empty($currentGroup)) { 204 $navbox[$current] = $currentGroup; 205 } 206 } 207 } 208 //echo '<pre>'; 209 //var_dump($navbox); 210 //echo '</pre>'; 211 212 return $navbox; 213 } 214 215 /** 216 * Handles the actual output creation 217 * 218 * @param string $mode Renderer mode (supported modes: xhtml) 219 * @param Doku_Renderer $renderer The renderer 220 * @param array $data The data from the handler() function 221 * 222 * @return bool If rendering was successful 223 */ 224 public function render($mode, Doku_Renderer $renderer, $data) { 225 if ($mode != 'xhtml') return false; 226 // Prevent caching 227 $renderer->info['cache'] = false; 228 229 // Build the beginnings of the table 230 $html = '<div class="pgnb_container"><table class="pgnb_table"><tr><th class="pgnb_title" colspan="2"><span class="pgnb_title_text">'; 231 232 // Placeholder for our xhtml formatted URL 233 $url = ''; 234 235 // Add in the title, parse it first to generate any URLs present 236 $html .= $this->urlRender($data['title']); 237 // Prepare for the groups 238 $html .= '</span></th></tr>'; 239 240 // Get rid of the title to iterate over the groups 241 array_shift($data); 242 243 // Placeholder for our Group 244 $ghtml = ''; 245 246 // Go through each item group and build their row 247 foreach ($data as $group => $items) { 248 // Placeholder for group HTML while we build it, Add in the group title, and prepare for the items 249 $ghtml = '<tr><th class="pgnb_group_title">'.$this->urlRender($group).'</th><td class="pgnb_group">'; 250 251 // Flag for formatting the child table 252 $subgroupPresent = false; 253 254 // Iterate over each subgroup, there will always be a 'default' 255 foreach ($items as $subgroup => $subitems) { 256 // Render all the links 257 $urls = $this->urlRender($subitems); 258 // Format into the list 259 $urls = str_replace("<a", "<li><a", $urls); 260 $urls = str_replace("</a>", "</a></li>", $urls); 261 262 // The base group 263 if ($subgroup == 'default') { 264 // Append the list of URLs 265 $ghtml .= '<div style="padding:0.25em;"><ul class="pgnb_list">'.$urls.'</ul></div>'; 266 } else { 267 // We are working with a subgroup, additional HTML tags required 268 // If we don't already have a child table for the subgroups, create one 269 if (!$subgroupPresent) { 270 // This is our first subgroup 271 $ghtml .= '<table class="pgnb_child_table">'; 272 // Turn on the switch 273 $subgroupPresent = true; 274 } 275 // Append the row for the subgroup 276 $ghtml .= '<tr><th class="pgnb_subgroup_title">'.$this->urlRender($subgroup).'</th><td class="pgnb_group"><div style=padding:0.25em;"<ul class="pgnb_list">'.$urls."</ul></div></td></tr>"; 277 } 278 } 279 280 // We had subgroups, close off the child table 281 if ($subgroupPresent) { 282 $ghtml .= '</table>'; 283 } 284 285 // Close the group 286 $ghtml .= '</td></tr>'; 287 288 // Append the group to our HTML 289 $html .= $ghtml; 290 // Reset our placeholder 291 $ghtml = ''; 292 } 293 294 // Close out the table 295 $html .= '</table></div>'; 296 297 $renderer->doc .= $html; 298 299 return true; 300 } 301 302 /** 303 * Handles rendering of DokuWiki links to URLs for all kinds of URL 304 * 305 * @param string $item The DokuWiki markup to be converted 306 * 307 * @return string The XHTML rendering of the markup 308 */ 309 private function urlRender($item) { 310 // Create the parser 311 $urlParser = & new Doku_Parser(); 312 // Add a handler 313 $urlParser->Handler = & new Doku_Handler(); 314 // Add all the parsing modes for various URLs 315 $urlParser->addMode('camelcaselink',new Doku_Parser_Mode_CamelCaseLink()); 316 $urlParser->addMode('internallink',new Doku_Parser_Mode_InternalLink()); 317 $urlParser->addMode('media',new Doku_Parser_Mode_Media()); 318 $urlParser->addMode('externallink',new Doku_Parser_Mode_ExternalLink()); 319 $urlParser->addMode('emaillink',new Doku_Parser_Mode_EmailLink()); 320 $urlParser->addMode('windowssharelink',new Doku_Parser_Mode_WindowsShareLink()); 321 $urlParser->addMode('filelink',new Doku_Parser_Mode_FileLink()); 322 $urlParser->addMode('eol',new Doku_Parser_Mode_Eol()); 323 // Parse the string into instructions 324 $instructions = $urlParser->parse($item); 325 // Create the renderer 326 $urlRenderer = & new Doku_Renderer_XHTML(); 327 // Iterate over each instruction 328 foreach ($instructions as $instruction) { 329 // Execute the callback against the renderer 330 call_user_func_array(array(&$urlRenderer, $instruction[0]), $instruction[1]); 331 } 332 // Extract the XHTML data 333 $url = $urlRenderer->doc; 334 // Return the XHTML excluding the <p> and </p> tags 335 return substr($url, 5, strlen($url)-11); 336 } 337} 338 339?>