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