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