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