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