xref: /plugin/siteexport/inc/toc.php (revision b324a1904db918260ecf6c6ae2a8ee1c5f7c9512)
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->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->functions->getMapID($elem['id'], $elem['anchor'], $check) : array();
94
95            if ( empty($elem['depth']) ) {
96                $elem['depth'] = count(explode('/', $elem['url']));
97            }
98            $CHECKDATA[] = $elem['url'];
99
100            if ( $startPageID == null )
101            {
102                $startPageID = $elem['mapID'][0];
103            }
104
105            if ( empty( $elem['name'] ) || $elem['name'] == noNs($elem['id']) ) {
106                $elem['name'] = $this->functions->getSiteTitle($elem['id']);
107            }
108
109            // Go on building mapXML
110            $this->shortenByTranslation($elem['mapURL'], true); // true to already remove all language stuff - false if not
111            foreach ( $elem['mapID'] as $VIEWID ) {
112                $MAPXML .= "\n\t<mapID target=\"$VIEWID\" url=\"" . $elem['mapURL'] . $anchor . "\"/>";
113            }
114
115            $elem['tocNS'] = getNS(cleanID($elem['url']));
116            $elem['tocNS'] = explode('/', $this->shortenByTranslation($elem['tocNS'], true));
117            $this->functions->debug->message("This will be the TOC elements data:", $elem, 1);
118
119            $this->__buildTOCTree($DATA, $elem['tocNS'], $elem);
120        }
121
122        $TOCXML .= $this->__writeTOCTree($DATA) . "\n</toc>";
123        $MAPXML .= "\n</map>";
124
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\n\n";
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! - Index elements do not have anything else below
152            if (noNS($elemToAdd['id']) == $conf['start']) {
153                $DATA[noNS($elemToAdd['id'])] = $elemToAdd;
154            } else {
155                $DATA[noNS($elemToAdd['id'])]['element'] = $elemToAdd;
156            }
157            return;
158        } else if (count($currentNSArray) == 1 && $currentNSArray[0] == '' && noNS($elemToAdd['id']) == $conf['start'])
159        {
160            // Wird gebraucht um die erste Ebene sauber zu bauen … kann aber irgendwelche Nebeneffekte haben
161            $DATA[noNS($elemToAdd['id'])] = $elemToAdd;
162            return;
163        }
164
165        $currentLevel = array_shift($currentNSArray);
166        $nextLevel = &$DATA[$currentLevel];
167        if (empty($nextLevel)) {
168            $nextLevel = array('pages' => array(), 'element' => array());
169        } else {
170            $nextLevel = &$DATA[$currentLevel]['pages'];
171        }
172
173        $this->__buildTOCTree($nextLevel, $currentNSArray, $elemToAdd);
174    }
175
176    /**
177     * Create a single TOC Item
178     **/
179    private function __TOCItem($item, $depth, $selfClosed = true)
180    {
181        $targetID = $item['mapID'][0];
182        if (empty($targetID)) {
183            $targetID = strtolower($item['name']);
184        }
185        return "\n" . str_repeat("\t", max($depth, 0)+1) . "<tocitem target=\"$targetID\"" . (intval($item['exists']) == 1 ? " text=\"" . $item['name'] . "\"" : "") . ($selfClosed ? '/' : '') . ">";
186    }
187
188    /**
189     * Create a single TOC Item
190     **/
191    private function __TOCItemClose($depth)
192    {
193        return "\n" . str_repeat("\t", max($depth, 0)+1) . "</tocitem>";
194    }
195
196    /**
197     * Write the whole TOC TREE
198     **/
199    private function __writeTOCTree($CURRENTNODE, $CURRENTNODENAME = null, $DEPTH = 0) {
200        global $conf;
201
202        $XML = '';
203        $didOpenItem = false;
204        if (!is_array($CURRENTNODE) || empty($CURRENTNODE))
205        {
206            // errr … no.
207            return $XML;
208        }
209
210        // This is an element!
211        if (!empty($CURRENTNODE['id']))
212        {
213            // This has to be an item!
214            return $this->__TOCItem($CURRENTNODE, $DEPTH);
215        }
216
217        // Look for start page
218        if (!empty($CURRENTNODE[$conf['start']]))
219        {
220            // YAY! StartPage found.
221            $didOpenItem = !(count(empty($CURRENTNODE['pages']) ? $CURRENTNODE : $CURRENTNODE['pages']) == 0);
222            $XML .= $this->__TOCItem($CURRENTNODE[$conf['start']], $DEPTH, !$didOpenItem);
223            unset($CURRENTNODE[$conf['start']]);
224        } else if (!empty($CURRENTNODE['element'])) {
225            $didOpenItem = !(count($CURRENTNODE['pages']) == 0);
226            $XML .= $this->__TOCItem($CURRENTNODE['element'], $DEPTH, !$didOpenItem);
227            unset($CURRENTNODE['element']);
228        } else if ($CURRENTNODENAME != null) {
229            // We have a parent node for what is comming … lets honor that
230            $didOpenItem = !(count($CURRENTNODE) == 0);
231            $XML .= $this->__TOCItem(array('name' => ucwords($CURRENTNODENAME)), $DEPTH, !$didOpenItem);
232        } else {
233            // Woohoo … empty node? do not count up!
234            $DEPTH--;
235        }
236
237        // Circle through the entries
238        foreach (empty($CURRENTNODE['pages']) ? $CURRENTNODE : $CURRENTNODE['pages'] as $NODENAME => $ELEM)
239        {
240            // a node should have more than only one entry … otherwise we will not tell our name!
241            $XML .= $this->__writeTOCTree($ELEM, count($ELEM) >= 1 ? $NODENAME : null, $DEPTH+1);
242        }
243
244        // Close and return
245        return $XML . ($didOpenItem ? $this->__TOCItemClose($DEPTH) : '');
246    }
247
248
249    function post(&$value, $key, array $additional) {
250        $inner_glue = $additional[0];
251        $prefix = isset($additional[1]) ? $additional[1] : false;
252        if ($prefix === false) $prefix = $key;
253
254        $value = $value . $inner_glue . $prefix;
255    }
256
257    /**
258     * internal Sort function
259     * @param unknown_type $a
260     * @param unknown_type $b
261     */
262    private function sortFunction($a, $b)
263    {
264        $idA = $a['id'];
265        $idB = $b['id'];
266
267        $depthA = explode(':', getNS($idA));
268        $depthB = explode(':', getNS($idB));
269
270        for ($i = 0; $i < min(count($depthA), count($depthB)); $i++)
271        {
272            $NSCMP = strcmp($depthA[$i], $depthB[$i]);
273            if ($NSCMP != 0)
274            {
275                // Something is different!
276                return $NSCMP;
277            }
278        }
279
280        // There is mor in B, than in A!
281        if (count($depthA) < count($depthB))
282        {
283            return -1;
284        } else if (count($depthA) > count($depthB))
285        {
286            // there is more in A than in B
287            return 1;
288        }
289
290        if ($NSCMP == 0)
291        {
292            // Something is different!
293            return strcmp(noNS($idA), noNS($idB));
294        }
295
296        return 0;
297    }
298
299    /**
300     * Build the Eclipse Documentation TOC XML
301     **/
302    public function __getTOCXML($DATA, $XML = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<?NLS TYPE=\"org.eclipse.help.toc\"?>\n") {
303
304        $pagesArray = array();
305
306        // Go through the pages
307        foreach ($DATA as $elem) {
308
309            $site = $elem['id'];
310            $elems = explode('/', $this->functions->getSiteName($site));
311
312            // Strip Site
313            array_pop($elems);
314
315            // build the topic Tree
316            $this->__buildTopicTree($pagesArray, $elems, $site);
317        }
318
319        $XML .= $this->__addXMLTopic($pagesArray, 'toc');
320
321        return $XML;
322
323    }
324
325    /**
326     * Load the topic Tree for the TOC - recursive
327     **/
328    private function __buildTopicTree(&$PAGES, $DATA, $SITE, $INSERTDATA = null) {
329
330        if (empty($DATA) || !is_array($DATA)) {
331
332            if ($INSERTDATA == null)
333            {
334                $INSERTDATA = $SITE;
335            }
336
337            // This is already a namespace
338            if (is_array($PAGES[noNS($SITE)])) {
339                // The root already exists!
340                if (!empty($PAGES[noNS($SITE)][noNS($SITE)])) {
341                    if (strstr($PAGES[noNS($SITE)][noNS($SITE)], $SITE)) {
342                        // The SITE is in the parent Namespace, and the current Namespace has an index with same name
343                        $PAGES['__' . noNS($SITE)] = $INSERTDATA;
344                    } else {
345                        $PAGES['__' . noNS($SITE)] = $PAGES[noNS($SITE)][noNS($SITE)];
346                        $PAGES[noNS($SITE)][noNS($SITE)] = $INSERTDATA;
347                    }
348                } else {
349                    $PAGES[noNS($SITE)][noNS($SITE)] = $INSERTDATA;
350                }
351            } else {
352                // just a Page
353                $PAGES[noNS($SITE)] = $INSERTDATA;
354            }
355            return;
356        }
357
358        $NS = array_shift($DATA);
359        if (!is_array($PAGES[$NS])) $PAGES[$NS] = empty($PAGES[$NS]) ? array() : array($PAGES[$NS]);
360        $this->__buildTopicTree($PAGES[$NS], $DATA, $SITE, $INSERTDATA);
361
362        return;
363    }
364
365    /**
366     * Build the Topic Tree for TOC.xml
367     **/
368    private function __addXMLTopic($DATA, $ITEM = 'topic', $LEVEL = 0, $NODENAME = '') {
369        global $conf;
370
371        $DEPTH = str_repeat("\t", $LEVEL);
372
373        if (!is_array($DATA)) {
374            return $DEPTH . '<' . $ITEM . ' label="' . $this->functions->getSiteTitle($DATA) . '" ' . ($ITEM != 'topic' ? 'topic' : 'href') . '="' . $this->functions->getSiteName($DATA) . "\" />\n";
375        }
376        // Is array from this point on
377        list($indexTitle, $indexFile) = $this->__getIndexItem($DATA, $NODENAME);
378
379        if (empty($indexTitle)) $indexTitle = $this->functions->getSiteTitle($conf['start']);
380        if (!empty($indexFile)) $indexFile = ($ITEM != 'topic' ? 'topic' : 'href') . "=\"$indexFile\"";
381
382        $isEmptyNode = count($DATA) == 1 && empty($indexFile);
383
384        if (!$isEmptyNode && ($this->emptyNSToc || count($DATA) > 0))
385        $XML = "$DEPTH<$ITEM label=\"$indexTitle\" $indexFile>";
386
387        if (!$isEmptyNode && count($DATA) > 0) $XML .= "\n";
388
389        foreach ($DATA as $NODENAME => $NS) {
390
391            $XML .= $this->__addXMLTopic($NS, (!($this->emptyNSToc || count($DATA) > 1) && $ITEM != 'topic' ? $ITEM : 'topic'), $LEVEL+(!$isEmptyNode ? 1 : 0), $NODENAME);
392
393        }
394
395        if (!$isEmptyNode && count($DATA) > 0) $XML .= "$DEPTH";
396        if (!$isEmptyNode && ($this->emptyNSToc || count($DATA) > 0)) {
397            $XML .= "</$ITEM>\n";
398        }
399
400        return $XML;
401    }
402
403
404    /**
405     * Get the context XML
406     **/
407    public function __getContextXML($DATA) {
408
409        $XML = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<?NLS TYPE=\"org.eclipse.help.context\"?>\n<contexts>\n";
410
411        $check = array();
412        foreach ($DATA as $elem)
413        {
414            $ID = $elem['id'];
415            $meta = p_get_metadata($ID, 'context', true);
416            if (empty($meta['id'])) { continue; }
417
418            $TITLE = empty($meta['title']) ? $this->functions->getSiteTitle($ID) : $meta['title'];
419
420            // support more than one view IDs ... for more than one reference
421            $VIEWIDs = $this->functions->getMapID($elem['id'], $elem['anchor'], $check);
422
423            $DESCRIPTION = $this->functions->xmlEntities(p_get_metadata($ID, 'description abstract'));
424
425            // Build topic Links
426            $url = $this->functions->getSiteName($ID);
427            $this->shortenByTranslation($url);
428
429            $TOPICS = array($url => $TITLE . " (Details)");
430            $REFS = p_get_metadata($ID, 'relation references', true);
431            if (is_array($REFS))
432            foreach ($REFS as $REL => $EXISTS) {
433                if (!$EXISTS) { continue; }
434                $TOPICS[$this->functions->getSiteName($REL)] = $this->functions->getSiteTitle($REL);
435            }
436
437            // build XML - include multi view IDs
438            foreach ($VIEWIDs as $VIEWID) {
439                $XML .= "\t<context id=\"$VIEWID\" title=\"$TITLE\">\n";
440                $XML .= "\t\t<description>$DESCRIPTION</description>\n";
441
442                foreach ($TOPICS as $URL => $LABEL) {
443                    $XML .= "\t\t<topic label=\"$LABEL\" href=\"$URL\" />\n";
444                }
445
446                $XML .= "\t</context>\n";
447            }
448        }
449
450        $XML .= "</contexts>";
451        return $XML;
452
453    }
454
455    /**
456     * Determine if this is an index - and if so, find its Title
457     **/
458    private function __getIndexItem(&$DATA, $NODENAME = '') {
459        global $conf;
460
461        if (!is_array($DATA)) { return; }
462
463        $indexTitle = '';
464        $indexFile = '';
465        foreach ($DATA as $NODE => $indexSearch) {
466            // Skip next Namespaces
467            if (is_array($indexSearch)) { continue; }
468
469            // Skip if this is not a start
470            if ($NODE != $conf['start']) { continue; }
471
472            $indexTitle = $this->functions->getSiteTitle($indexSearch);
473            $indexFile = $indexSearch;
474            unset($DATA[$NODE]);
475            break;
476        }
477
478        if (empty($indexFile) && !empty($DATA[$NODENAME])) {
479            $indexTitle = $this->functions->getSiteTitle($DATA[$NODENAME]);
480            $indexFile = $DATA[$NODENAME];
481            unset($DATA[$NODENAME]);
482        }
483
484        return array($indexTitle, $this->functions->getSiteName($indexFile));
485    }
486}
487
488