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