xref: /plugin/siteexport/inc/toc.php (revision cbb6b50ef00aab5dc836e0ee91d910739f5338ba)
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        $URLcount = count($url);
30        for ($i = 0; $i < $URLcount ; $i++)
31        {
32            if (in_array($url[$i], $checkArray))
33            {
34                // Rauswerfen und weg
35                $url[$i] = '';
36                break;
37            }
38
39            if (!$deepSearch)
40            {
41                break;
42            }
43
44            // Ok, remove anyway
45            $url[$i] = '';
46        }
47
48        $inputURL = implode('/', $url);
49        $inputURL = preg_replace("$\/+$", "/", $inputURL);
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
71        // Go through the pages
72        $CHECKDATA = array();
73        $nData = $DATA;
74        $DATA = array();
75        $check = array();
76        $startPageID = null;
77
78        foreach ( $nData as $elem )
79        {
80            // Check if available
81            $anchor = ( !empty($elem['anchor']) ? '#' . $elem['anchor'] : '' );
82            $elem['url'] = $this->functions->getSiteName($elem['id'], true); // Override - we need a clean name
83            $elem['mapURL'] = $elem['url'];
84            $this->shortenByTranslation($elem['url']);
85
86            // only add an url once
87            if ( in_array($elem['url'], $CHECKDATA) ) { continue; }
88
89            if ( !isset($elem['exists']) ) {
90                resolve_pageid(getNS($elem['id']),$elem['id'],$elem['exists']);
91                $this->functions->debug->message("EXISTS previously not set.", $elem, 1);
92            }
93
94            // if not there, no map ids will be generated
95            $elem['mapID'] = intval($elem['exists']) == 1 ? $this->functions->getMapID($elem['id'], $elem['anchor'], $check) : array();
96
97            if ( empty($elem['depth']) ) {
98                $elem['depth'] = count(explode('/', $elem['url']));
99            }
100            $CHECKDATA[] = $elem['url'];
101
102            if ( $startPageID == null )
103            {
104                $startPageID = $elem['mapID'][0];
105            }
106
107            if ( empty( $elem['name'] ) || $elem['name'] == noNs($elem['id']) ) {
108                $elem['name'] = $this->functions->getSiteTitle($elem['id']);
109                $this->debug($elem);
110            }
111
112            // Go on building mapXML
113            $this->shortenByTranslation($elem['mapURL'], true); // true to already remove all language stuff - false if not
114            foreach ( $elem['mapID'] as $VIEWID ) {
115                $MAPXML .= "\n\t<mapID target=\"$VIEWID\" url=\"" . $elem['mapURL'] . $anchor . "\"/>";
116            }
117
118            $elem['tocNS'] = getNS(cleanID($elem['url']));
119            $elem['tocNS'] = $this->shortenByTranslation($elem['tocNS'], true);
120            $elem['tocNS'] = strlen($elem['tocNS']) > 0 ? explode('/', $elem['tocNS']) : array();
121            $this->functions->debug->message("This will be the TOC elements data:", $elem, 1);
122
123            $this->__buildTOCTree($DATA, $elem['tocNS'], $elem);
124        }
125
126        $this->debug("#### Writing TOC Tree ####");
127        $TOCXML .= $this->__writeTOCTree($DATA) . "\n</toc>";
128        $this->debug("#### DONE: Writing TOC Tree ####");
129        $MAPXML .= "\n</map>";
130
131        $this->debug($DATA);
132        $this->debug($TOCXML);
133        $this->debug($MAPXML);
134
135        return array($TOCXML, $MAPXML, $startPageID);
136    }
137
138    /**
139     * Prepare the TOC Tree
140     **/
141    private function __buildTOCTree(&$DATA, $currentNSArray, $elemToAdd)
142    {
143        global $conf;
144
145        // Actual level
146        if (empty($currentNSArray)) {
147            $elemToAdd['isStartPage'] = noNS($elemToAdd['id']) == $conf['start'];
148            // $key = empty($elemToAdd['name']) || 1==1 ? noNS($elemToAdd['id']) : $elemToAdd['name'];
149            $key = noNS($elemToAdd['id']);
150            $DATA[$key] = $elemToAdd;
151            return;
152        }
153
154        $currentLevel = array_shift($currentNSArray);
155        $nextLevel = &$DATA[$currentLevel];
156        if (empty($nextLevel)) {
157            $nextLevel = array('pages' => array());
158        } else {
159            $nextLevel = &$DATA[$currentLevel]['pages'];
160        }
161
162        $this->__buildTOCTree($nextLevel, $currentNSArray, $elemToAdd);
163    }
164
165    /**
166     * Create a single TOC Item
167     **/
168    private function __TOCItem($item, $depth, $selfClosed = true)
169    {
170        $this->debug($item);
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']) && empty($CURRENTNODE['pages']))
202        {
203            // This has to be an item - only -!
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(empty($CURRENTNODE['pages']) ? $CURRENTNODE : $CURRENTNODE['pages']) == 0);
212            $XML .= $this->__TOCItem($CURRENTNODE[$conf['start']], $DEPTH, !$didOpenItem);
213            unset($CURRENTNODE[$conf['start']]);
214        } else if (!empty($CURRENTNODE['element'])) {
215            $didOpenItem = !(count($CURRENTNODE['pages']) == 0);
216            $XML .= $this->__TOCItem($CURRENTNODE['element'], $DEPTH, !$didOpenItem);
217            unset($CURRENTNODE['element']);
218        } else if ($CURRENTNODENAME != null) {
219            // We have a parent node for what is comming … lets honor that
220            $didOpenItem = !(count($CURRENTNODE) == 0);
221            $XML .= $this->__TOCItem(array('name' => ucwords($CURRENTNODENAME)), $DEPTH, !$didOpenItem);
222        } else {
223            // Woohoo … empty node? do not count up!
224            $DEPTH--;
225        }
226
227        $this->debug("-- This is the current node --");
228        $this->debug($CURRENTNODE);
229
230        // Circle through the entries
231        foreach (empty($CURRENTNODE['pages']) ? $CURRENTNODE : $CURRENTNODE['pages'] as $NODENAME => $ELEM)
232        {
233            // a node should have more than only one entry … otherwise we will not tell our name!
234            $XML .= $this->__writeTOCTree($ELEM, count($ELEM) >= 1 ? ( !empty($ELEM['name']) ? $ELEM['name'] : $NODENAME ) : null, $DEPTH+1);
235        }
236
237        // Close and return
238        return $XML . ($didOpenItem ? $this->__TOCItemClose($DEPTH) : '');
239    }
240
241    /**
242     * Build the Eclipse Documentation TOC XML
243     **/
244    public function __getTOCXML($DATA, $XML = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<?NLS TYPE=\"org.eclipse.help.toc\"?>\n") {
245
246        $pagesArray = array();
247
248        // Go through the pages
249        foreach ($DATA as $elem) {
250
251            $site = $elem['id'];
252            $elems = explode('/', $this->functions->getSiteName($site));
253
254            // Strip Site
255            array_pop($elems);
256
257            // build the topic Tree
258            $this->__buildTopicTree($pagesArray, $elems, $site);
259        }
260
261        $XML .= $this->__addXMLTopic($pagesArray, 'toc');
262
263        return $XML;
264
265    }
266
267    /**
268     * Load the topic Tree for the TOC - recursive
269     **/
270    private function __buildTopicTree(&$PAGES, $DATA, $SITE, $INSERTDATA = null) {
271
272        if (empty($DATA) || !is_array($DATA)) {
273
274            if ($INSERTDATA == null)
275            {
276                $INSERTDATA = $SITE;
277            }
278
279            // This is already a namespace
280            if (is_array($PAGES[noNS($SITE)])) {
281                // The root already exists!
282                if (!empty($PAGES[noNS($SITE)][noNS($SITE)])) {
283                    if (strstr($PAGES[noNS($SITE)][noNS($SITE)], $SITE)) {
284                        // The SITE is in the parent Namespace, and the current Namespace has an index with same name
285                        $PAGES['__' . noNS($SITE)] = $INSERTDATA;
286                    } else {
287                        $PAGES['__' . noNS($SITE)] = $PAGES[noNS($SITE)][noNS($SITE)];
288                        $PAGES[noNS($SITE)][noNS($SITE)] = $INSERTDATA;
289                    }
290                } else {
291                    $PAGES[noNS($SITE)][noNS($SITE)] = $INSERTDATA;
292                }
293            } else {
294                // just a Page
295                $PAGES[noNS($SITE)] = $INSERTDATA;
296            }
297            return;
298        }
299
300        $NS = array_shift($DATA);
301        if (!is_array($PAGES[$NS])) $PAGES[$NS] = empty($PAGES[$NS]) ? array() : array($PAGES[$NS]);
302        $this->__buildTopicTree($PAGES[$NS], $DATA, $SITE, $INSERTDATA);
303
304        return;
305    }
306
307    /**
308     * Build the Topic Tree for TOC.xml
309     **/
310    private function __addXMLTopic($DATA, $ITEM = 'topic', $LEVEL = 0, $NODENAME = '') {
311        global $conf;
312
313        $DEPTH = str_repeat("\t", $LEVEL);
314
315        if (!is_array($DATA)) {
316            return $DEPTH . '<' . $ITEM . ' label="' . $this->functions->getSiteTitle($DATA) . '" ' . ($ITEM != 'topic' ? 'topic' : 'href') . '="' . $this->functions->getSiteName($DATA) . "\" />\n";
317        }
318        // Is array from this point on
319        list($indexTitle, $indexFile) = $this->__getIndexItem($DATA, $NODENAME);
320
321        if (empty($indexTitle)) $indexTitle = $this->functions->getSiteTitle($conf['start']);
322        if (!empty($indexFile)) $indexFile = ($ITEM != 'topic' ? 'topic' : 'href') . "=\"$indexFile\"";
323
324        $isEmptyNode = count($DATA) == 1 && empty($indexFile);
325
326        if (!$isEmptyNode && ($this->emptyNSToc || count($DATA) > 0)) {
327            $XML = "$DEPTH<$ITEM label=\"$indexTitle\" $indexFile>";
328        } else {
329            $XML = "";
330        }
331
332        if (!$isEmptyNode && count($DATA) > 0) $XML .= "\n";
333
334        foreach ($DATA as $NODENAME => $NS) {
335            $XML .= $this->__addXMLTopic($NS, (!($this->emptyNSToc || count($DATA) > 1) && $ITEM != 'topic' ? $ITEM : 'topic'), $LEVEL+(!$isEmptyNode ? 1 : 0), $NODENAME);
336        }
337
338        if (!$isEmptyNode && count($DATA) > 0) $XML .= "$DEPTH";
339        if (!$isEmptyNode && ($this->emptyNSToc || count($DATA) > 0)) {
340            $XML .= "</$ITEM>\n";
341        }
342
343        return $XML;
344    }
345
346
347    /**
348     * Get the context XML
349     **/
350    public function __getContextXML($DATA) {
351
352        $XML = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<?NLS TYPE=\"org.eclipse.help.context\"?>\n<contexts>\n";
353
354        $check = array();
355        foreach ($DATA as $elem)
356        {
357            $ID = $elem['id'];
358            $meta = p_get_metadata($ID, 'context', true);
359            if (empty($meta['id'])) { continue; }
360
361            $TITLE = empty($meta['title']) ? $this->functions->getSiteTitle($ID) : $meta['title'];
362
363            // support more than one view IDs ... for more than one reference
364            $VIEWIDs = $this->functions->getMapID($elem['id'], $elem['anchor'], $check);
365
366            $DESCRIPTION = $this->functions->xmlEntities(p_get_metadata($ID, 'description abstract'));
367
368            // Build topic Links
369            $url = $this->functions->getSiteName($ID);
370            $this->shortenByTranslation($url);
371
372            $TOPICS = array($url => $TITLE . " (Details)");
373            $REFS = p_get_metadata($ID, 'relation references', true);
374            if (is_array($REFS))
375            foreach ($REFS as $REL => $EXISTS) {
376                if (!$EXISTS) { continue; }
377                $TOPICS[$this->functions->getSiteName($REL)] = $this->functions->getSiteTitle($REL);
378            }
379
380            // build XML - include multi view IDs
381            foreach ($VIEWIDs as $VIEWID) {
382                $XML .= "\t<context id=\"$VIEWID\" title=\"$TITLE\">\n";
383                $XML .= "\t\t<description>$DESCRIPTION</description>\n";
384
385                foreach ($TOPICS as $URL => $LABEL) {
386                    $XML .= "\t\t<topic label=\"$LABEL\" href=\"$URL\" />\n";
387                }
388
389                $XML .= "\t</context>\n";
390            }
391        }
392
393        $XML .= "</contexts>";
394        return $XML;
395
396    }
397
398    /**
399     * Determine if this is an index - and if so, find its Title
400     **/
401    private function __getIndexItem(&$DATA, $NODENAME = '') {
402        global $conf;
403
404        if (!is_array($DATA)) { return; }
405
406        $indexTitle = '';
407        $indexFile = '';
408        foreach ($DATA as $NODE => $indexSearch) {
409            // Skip next Namespaces
410            if (is_array($indexSearch)) { continue; }
411
412            // Skip if this is not a start
413            if ($NODE != $conf['start']) { continue; }
414
415            $indexTitle = $this->functions->getSiteTitle($indexSearch);
416            $indexFile = $indexSearch;
417            unset($DATA[$NODE]);
418            break;
419        }
420
421        if (empty($indexFile) && !empty($DATA[$NODENAME])) {
422            $indexTitle = $this->functions->getSiteTitle($DATA[$NODENAME]);
423            $indexFile = $DATA[$NODENAME];
424            unset($DATA[$NODENAME]);
425        }
426
427        return array($indexTitle, $this->functions->getSiteName($indexFile));
428    }
429
430    private $doDebug = false;
431    private static $didDebug = false;
432    public function debug($data, $final = false) {
433        if ( ! $this->doDebug ) { return; }
434
435        if ( !$this->didDebug ) {
436            print "<html><pre>";
437            $this->didDebug = true;
438        }
439
440        if ( is_array($data) ) {
441            print_r($data);
442        } else {
443            print str_replace("<", "&lt;", str_replace(">", "&gt;", $data));;
444        }
445
446        print "\n\n";
447
448        if ( $final ) {
449            print "</pre></html>";
450            exit;
451        }
452    }
453}
454