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