1<?php 2 3if (!defined('DOKU_PLUGIN')) die('meh'); 4 5class siteexport_toc 6{ 7 private $emptyNSToc = true; 8 private $functions = null; 9 private $NS = null; 10 public $translation = null; 11 12 public function __construct($functions, $NS) 13 { 14 $this->doDebug = !empty($_REQUEST['tocDebug']); 15 $this->emptyNSToc = !empty($_REQUEST['emptyTocElem']); 16 $this->functions = $functions; 17 $this->NS = $NS; 18 } 19 20 private function shortenByTranslation(&$inputURL, $deepSearch = false) 21 { 22 // Mandatory: we allways want '/' insteadf of ':' here 23 $inputURL = str_replace(':', '/', $inputURL); 24 $checkArray = $this->translation ? $this->translation->trans : array(array_pop(explode(':', $this->NS))); 25 26 $url = explode('/', $inputURL); 27 28 for ($i = 0; $i < count($url); $i++) 29 { 30 if (in_array($url[$i], $checkArray)) 31 { 32 // Rauswerfen und weg 33 $url[$i] = ''; 34 break; 35 } 36 37 if (!$deepSearch) 38 { 39 break; 40 } 41 42 // Ok, remove anyway 43 $url[$i] = ''; 44 } 45 46 $inputURL = implode('/', $url); 47 $inputURL = preg_replace("$\/+$", "/", $inputURL); 48 49 if (strlen($inputURL) > 0 && substr($inputURL, 0, 1) == '/') 50 { 51 $inputURL = substr($inputURL, 1); 52 } 53 54 return $inputURL; 55 } 56 57 /** 58 * Build the Java Documentation TOC XML 59 **/ 60 public function __getJavaHelpTOCXML($DATA) { 61 62 if (count($DATA) == 0) { 63 return false; 64 } 65 66 $TOCXML = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<toc>"; 67 $MAPXML = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<map version=\"1.0\">"; 68 // usort($DATA, array($this, 'sortFunction')); 69 70 // Go through the pages 71 $CHECKDATA = array(); 72 $nData = $DATA; 73 $DATA = array(); 74 $check = array(); 75 $startPageID = null; 76 77 foreach ( $nData as $elem ) 78 { 79 // Check if available 80 $anchor = ( !empty($elem['anchor']) ? '#' . $elem['anchor'] : '' ); 81 $elem['url'] = $this->functions->getSiteName($elem['id'], true); // Override - we need a clean name 82 $elem['mapURL'] = $elem['url']; 83 $this->shortenByTranslation($elem['url']); 84 85 // only add an url once 86 if ( in_array($elem['url'], $CHECKDATA) ) { continue; } 87 88 if ( !isset($elem['exists']) ) { 89 resolve_pageid(getNS($elem['id']),$elem['id'],$elem['exists']); 90 $this->functions->debug->message("EXISTS previously not set.", $elem, 1); 91 } 92 93 // if not there, no map ids will be generated 94 $elem['mapID'] = intval($elem['exists']) == 1 ? $this->functions->getMapID($elem['id'], $elem['anchor'], $check) : array(); 95 96 if ( empty($elem['depth']) ) { 97 $elem['depth'] = count(explode('/', $elem['url'])); 98 } 99 $CHECKDATA[] = $elem['url']; 100 101 if ( $startPageID == null ) 102 { 103 $startPageID = $elem['mapID'][0]; 104 } 105 106 if ( empty( $elem['name'] ) || $elem['name'] == noNs($elem['id']) ) { 107 $elem['name'] = $this->functions->getSiteTitle($elem['id']); 108 } 109 110 // Go on building mapXML 111 $this->shortenByTranslation($elem['mapURL'], true); // true to already remove all language stuff - false if not 112 foreach ( $elem['mapID'] as $VIEWID ) { 113 $MAPXML .= "\n\t<mapID target=\"$VIEWID\" url=\"" . $elem['mapURL'] . $anchor . "\"/>"; 114 } 115 116 $elem['tocNS'] = getNS(cleanID($elem['url'])); 117 $elem['tocNS'] = $this->shortenByTranslation($elem['tocNS'], true); 118 $elem['tocNS'] = strlen($elem['tocNS']) > 0 ? explode('/', $elem['tocNS']) : array(); 119 $this->functions->debug->message("This will be the TOC elements data:", $elem, 1); 120 121 $this->__buildTOCTree($DATA, $elem['tocNS'], $elem); 122 } 123 124 $TOCXML .= $this->__writeTOCTree($DATA) . "\n</toc>"; 125 $MAPXML .= "\n</map>"; 126 127 $this->debug($DATA); 128 $this->debug($TOCXML); 129 $this->debug($MAPXML, true); 130 131 return array($TOCXML, $MAPXML, $startPageID); 132 } 133 134 /** 135 * Prepare the TOC Tree 136 **/ 137 private function __buildTOCTree(&$DATA, $currentNSArray, $elemToAdd) 138 { 139 global $conf; 140 141 // Actual level 142 if (empty($currentNSArray)) { 143 $DATA[noNS($elemToAdd['id'])] = $elemToAdd; 144 return; 145 } 146 147 $currentLevel = array_shift($currentNSArray); 148 $nextLevel = &$DATA[$currentLevel]; 149 if (empty($nextLevel)) { 150 $nextLevel = array('pages' => array()); 151 } else { 152 $nextLevel = &$DATA[$currentLevel]['pages']; 153 } 154 155 $this->__buildTOCTree($nextLevel, $currentNSArray, $elemToAdd); 156 } 157 158 /** 159 * Create a single TOC Item 160 **/ 161 private function __TOCItem($item, $depth, $selfClosed = true) 162 { 163 $targetID = $item['mapID'][0]; 164 if (empty($targetID)) { 165 $targetID = strtolower($item['name']); 166 } 167 return "\n" . str_repeat("\t", max($depth, 0)+1) . "<tocitem target=\"$targetID\"" . (intval($item['exists']) == 1 ? " text=\"" . $item['name'] . "\"" : "") . ($selfClosed ? '/' : '') . ">"; 168 } 169 170 /** 171 * Create a single TOC Item 172 **/ 173 private function __TOCItemClose($depth) 174 { 175 return "\n" . str_repeat("\t", max($depth, 0)+1) . "</tocitem>"; 176 } 177 178 /** 179 * Write the whole TOC TREE 180 **/ 181 private function __writeTOCTree($CURRENTNODE, $CURRENTNODENAME = null, $DEPTH = 0) { 182 global $conf; 183 184 $XML = ''; 185 $didOpenItem = false; 186 if (!is_array($CURRENTNODE) || empty($CURRENTNODE)) 187 { 188 // errr … no. 189 return $XML; 190 } 191 192 // This is an element! 193 if (!empty($CURRENTNODE['id']) && empty($CURRENTNODE['pages'])) 194 { 195 // This has to be an item - only -! 196 return $this->__TOCItem($CURRENTNODE, $DEPTH); 197 } 198 199 // Look for start page 200 if (!empty($CURRENTNODE[$conf['start']])) 201 { 202 // YAY! StartPage found. 203 $didOpenItem = !(count(empty($CURRENTNODE['pages']) ? $CURRENTNODE : $CURRENTNODE['pages']) == 0); 204 $XML .= $this->__TOCItem($CURRENTNODE[$conf['start']], $DEPTH, !$didOpenItem); 205 unset($CURRENTNODE[$conf['start']]); 206 } else if (!empty($CURRENTNODE['element'])) { 207 $didOpenItem = !(count($CURRENTNODE['pages']) == 0); 208 $XML .= $this->__TOCItem($CURRENTNODE['element'], $DEPTH, !$didOpenItem); 209 unset($CURRENTNODE['element']); 210 } else if ($CURRENTNODENAME != null) { 211 // We have a parent node for what is comming … lets honor that 212 $didOpenItem = !(count($CURRENTNODE) == 0); 213 $XML .= $this->__TOCItem(array('name' => ucwords($CURRENTNODENAME)), $DEPTH, !$didOpenItem); 214 } else { 215 // Woohoo … empty node? do not count up! 216 $DEPTH--; 217 } 218 219 // Circle through the entries 220 foreach (empty($CURRENTNODE['pages']) ? $CURRENTNODE : $CURRENTNODE['pages'] as $NODENAME => $ELEM) 221 { 222 // a node should have more than only one entry … otherwise we will not tell our name! 223 $XML .= $this->__writeTOCTree($ELEM, count($ELEM) >= 1 ? $NODENAME : null, $DEPTH+1); 224 } 225 226 // Close and return 227 return $XML . ($didOpenItem ? $this->__TOCItemClose($DEPTH) : ''); 228 } 229 230 231 function post(&$value, $key, array $additional) { 232 $inner_glue = $additional[0]; 233 $prefix = isset($additional[1]) ? $additional[1] : false; 234 if ($prefix === false) $prefix = $key; 235 236 $value = $value . $inner_glue . $prefix; 237 } 238 239 /** 240 * internal Sort function 241 * @param unknown_type $a 242 * @param unknown_type $b 243 */ 244 private function sortFunction($a, $b) 245 { 246 $idA = $a['id']; 247 $idB = $b['id']; 248 249 $depthA = explode(':', getNS($idA)); 250 $depthB = explode(':', getNS($idB)); 251 252 for ($i = 0; $i < min(count($depthA), count($depthB)); $i++) 253 { 254 $NSCMP = strcmp($depthA[$i], $depthB[$i]); 255 if ($NSCMP != 0) 256 { 257 // Something is different! 258 return $NSCMP; 259 } 260 } 261 262 // There is mor in B, than in A! 263 if (count($depthA) < count($depthB)) 264 { 265 return -1; 266 } else if (count($depthA) > count($depthB)) 267 { 268 // there is more in A than in B 269 return 1; 270 } 271 272 if ($NSCMP == 0) 273 { 274 // Something is different! 275 return strcmp(noNS($idA), noNS($idB)); 276 } 277 278 return 0; 279 } 280 281 /** 282 * Build the Eclipse Documentation TOC XML 283 **/ 284 public function __getTOCXML($DATA, $XML = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<?NLS TYPE=\"org.eclipse.help.toc\"?>\n") { 285 286 $pagesArray = array(); 287 288 // Go through the pages 289 foreach ($DATA as $elem) { 290 291 $site = $elem['id']; 292 $elems = explode('/', $this->functions->getSiteName($site)); 293 294 // Strip Site 295 array_pop($elems); 296 297 // build the topic Tree 298 $this->__buildTopicTree($pagesArray, $elems, $site); 299 } 300 301 $XML .= $this->__addXMLTopic($pagesArray, 'toc'); 302 303 return $XML; 304 305 } 306 307 /** 308 * Load the topic Tree for the TOC - recursive 309 **/ 310 private function __buildTopicTree(&$PAGES, $DATA, $SITE, $INSERTDATA = null) { 311 312 if (empty($DATA) || !is_array($DATA)) { 313 314 if ($INSERTDATA == null) 315 { 316 $INSERTDATA = $SITE; 317 } 318 319 // This is already a namespace 320 if (is_array($PAGES[noNS($SITE)])) { 321 // The root already exists! 322 if (!empty($PAGES[noNS($SITE)][noNS($SITE)])) { 323 if (strstr($PAGES[noNS($SITE)][noNS($SITE)], $SITE)) { 324 // The SITE is in the parent Namespace, and the current Namespace has an index with same name 325 $PAGES['__' . noNS($SITE)] = $INSERTDATA; 326 } else { 327 $PAGES['__' . noNS($SITE)] = $PAGES[noNS($SITE)][noNS($SITE)]; 328 $PAGES[noNS($SITE)][noNS($SITE)] = $INSERTDATA; 329 } 330 } else { 331 $PAGES[noNS($SITE)][noNS($SITE)] = $INSERTDATA; 332 } 333 } else { 334 // just a Page 335 $PAGES[noNS($SITE)] = $INSERTDATA; 336 } 337 return; 338 } 339 340 $NS = array_shift($DATA); 341 if (!is_array($PAGES[$NS])) $PAGES[$NS] = empty($PAGES[$NS]) ? array() : array($PAGES[$NS]); 342 $this->__buildTopicTree($PAGES[$NS], $DATA, $SITE, $INSERTDATA); 343 344 return; 345 } 346 347 /** 348 * Build the Topic Tree for TOC.xml 349 **/ 350 private function __addXMLTopic($DATA, $ITEM = 'topic', $LEVEL = 0, $NODENAME = '') { 351 global $conf; 352 353 $DEPTH = str_repeat("\t", $LEVEL); 354 355 if (!is_array($DATA)) { 356 return $DEPTH . '<' . $ITEM . ' label="' . $this->functions->getSiteTitle($DATA) . '" ' . ($ITEM != 'topic' ? 'topic' : 'href') . '="' . $this->functions->getSiteName($DATA) . "\" />\n"; 357 } 358 // Is array from this point on 359 list($indexTitle, $indexFile) = $this->__getIndexItem($DATA, $NODENAME); 360 361 if (empty($indexTitle)) $indexTitle = $this->functions->getSiteTitle($conf['start']); 362 if (!empty($indexFile)) $indexFile = ($ITEM != 'topic' ? 'topic' : 'href') . "=\"$indexFile\""; 363 364 $isEmptyNode = count($DATA) == 1 && empty($indexFile); 365 366 if (!$isEmptyNode && ($this->emptyNSToc || count($DATA) > 0)) 367 $XML = "$DEPTH<$ITEM label=\"$indexTitle\" $indexFile>"; 368 369 if (!$isEmptyNode && count($DATA) > 0) $XML .= "\n"; 370 371 foreach ($DATA as $NODENAME => $NS) { 372 373 $XML .= $this->__addXMLTopic($NS, (!($this->emptyNSToc || count($DATA) > 1) && $ITEM != 'topic' ? $ITEM : 'topic'), $LEVEL+(!$isEmptyNode ? 1 : 0), $NODENAME); 374 375 } 376 377 if (!$isEmptyNode && count($DATA) > 0) $XML .= "$DEPTH"; 378 if (!$isEmptyNode && ($this->emptyNSToc || count($DATA) > 0)) { 379 $XML .= "</$ITEM>\n"; 380 } 381 382 return $XML; 383 } 384 385 386 /** 387 * Get the context XML 388 **/ 389 public function __getContextXML($DATA) { 390 391 $XML = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<?NLS TYPE=\"org.eclipse.help.context\"?>\n<contexts>\n"; 392 393 $check = array(); 394 foreach ($DATA as $elem) 395 { 396 $ID = $elem['id']; 397 $meta = p_get_metadata($ID, 'context', true); 398 if (empty($meta['id'])) { continue; } 399 400 $TITLE = empty($meta['title']) ? $this->functions->getSiteTitle($ID) : $meta['title']; 401 402 // support more than one view IDs ... for more than one reference 403 $VIEWIDs = $this->functions->getMapID($elem['id'], $elem['anchor'], $check); 404 405 $DESCRIPTION = $this->functions->xmlEntities(p_get_metadata($ID, 'description abstract')); 406 407 // Build topic Links 408 $url = $this->functions->getSiteName($ID); 409 $this->shortenByTranslation($url); 410 411 $TOPICS = array($url => $TITLE . " (Details)"); 412 $REFS = p_get_metadata($ID, 'relation references', true); 413 if (is_array($REFS)) 414 foreach ($REFS as $REL => $EXISTS) { 415 if (!$EXISTS) { continue; } 416 $TOPICS[$this->functions->getSiteName($REL)] = $this->functions->getSiteTitle($REL); 417 } 418 419 // build XML - include multi view IDs 420 foreach ($VIEWIDs as $VIEWID) { 421 $XML .= "\t<context id=\"$VIEWID\" title=\"$TITLE\">\n"; 422 $XML .= "\t\t<description>$DESCRIPTION</description>\n"; 423 424 foreach ($TOPICS as $URL => $LABEL) { 425 $XML .= "\t\t<topic label=\"$LABEL\" href=\"$URL\" />\n"; 426 } 427 428 $XML .= "\t</context>\n"; 429 } 430 } 431 432 $XML .= "</contexts>"; 433 return $XML; 434 435 } 436 437 /** 438 * Determine if this is an index - and if so, find its Title 439 **/ 440 private function __getIndexItem(&$DATA, $NODENAME = '') { 441 global $conf; 442 443 if (!is_array($DATA)) { return; } 444 445 $indexTitle = ''; 446 $indexFile = ''; 447 foreach ($DATA as $NODE => $indexSearch) { 448 // Skip next Namespaces 449 if (is_array($indexSearch)) { continue; } 450 451 // Skip if this is not a start 452 if ($NODE != $conf['start']) { continue; } 453 454 $indexTitle = $this->functions->getSiteTitle($indexSearch); 455 $indexFile = $indexSearch; 456 unset($DATA[$NODE]); 457 break; 458 } 459 460 if (empty($indexFile) && !empty($DATA[$NODENAME])) { 461 $indexTitle = $this->functions->getSiteTitle($DATA[$NODENAME]); 462 $indexFile = $DATA[$NODENAME]; 463 unset($DATA[$NODENAME]); 464 } 465 466 return array($indexTitle, $this->functions->getSiteName($indexFile)); 467 } 468 469 private $doDebug = false; 470 private static $didDebug = false; 471 private function debug($data, $final = false) { 472 if ( ! $this->doDebug ) { return; } 473 474 if ( !$this->didDebug ) { 475 print "<html><pre>"; 476 $this->didDebug = true; 477 } 478 479 if ( is_array($data) ) { 480 print_r($data); 481 } else { 482 print str_replace("<", "<", str_replace(">", ">", $data));; 483 } 484 485 print "\n\n"; 486 487 if ( $final ) { 488 print "</pre></html>"; 489 exit; 490 } 491 } 492} 493