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