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