source: t29-www/lib/menu.php @ 736

Last change on this file since 736 was 649, checked in by sven, 10 years ago

RSS-Feed-Probebetrieb ausgeweitet: Internationalisiert, fuer englische Version gibts jetzt auch einen RSS-Feed.

Insbesondere ist in auf englischen Newsseite jetzt testweise ein alternatives Format zum Deutschen geschaltet, auf welches das Deutsche dann auch umgeschaltet werden soll. Es bedarf weniger Pflege und generiert gleichzeitig die Newsseite, das Newsmenü und den abonnierbaren News-Feed. Wenn sich das Format bewährt, kann die deutsche Newsseite darauf auch umgestellt werden.

Ferner wurden die RSS-Feeds auf den News-Seiten textuell erwähnt. Testweise werden auch im englischen Menü wieder Vorschaubilder (Thumbnails) bei den Neuigkeiten eingeblendet, die allerdings derzeit nur auf max 64x64px verkleinerte Einblendungen der entsprechenden News-Bilder sind. Hierfür fehlt noch eine sinnvollere Methode.

  • Property svn:keywords set to Id
File size: 14.4 KB
Line 
1<?php
2/**
3 * Needs conf:
4 *  webroot lang_path lang seiten_id languages
5 *
6 **/
7
8require_once dirname(__FILE__).'/messages.php';
9require_once dirname(__FILE__).'/logging.php';
10 
11class t29Menu {
12        public $conf;
13        public $xml;
14        public $log; // just for convenience
15
16        // Bevor es eine ordentliche Dev-Moeglichkeit gibt: Der magische
17        // Schalter zum Ausblenden der Geraeteseiten im Menue
18        public $hide_geraete_seiten = true;
19
20        // jeweils relativ zum lang_path
21        const navigation_file = 'navigation.xml';
22        const news_file = 'news.php';
23
24        // xpath queries to the navigation elements
25        const horizontal_menu = '/html/nav[@class="horizontal"]';
26        const sidebar_menu = '/html/nav[@class="side"]';
27
28        function __construct($conf_array) {
29                $this->conf = $conf_array;
30                $this->log = t29Log::get(); // just for convenience
31               
32                // create a message object if not given
33                if(!isset($this->conf['msg']))
34                        $this->conf['msg'] = new t29Messages($this->conf['lang']);
35               
36                // libxml: don't raise errors while parsing.
37                // will fetch them with libxml_get_errors later.
38                //libxml_use_internal_errors(true);
39
40                // load xml file
41                $this->xml = simplexml_load_file($this->conf['webroot'].$this->conf['lang_path'] . '/' . self::navigation_file);
42                if($this->xml_is_defective()) {
43                        trigger_error("Kann Navigationsdatei nicht verwenden, da das XML nicht sauber ist. Bitte reparieren!");
44                }
45        }
46
47        function xml_is_defective() {
48                // check if return value of simplexml_load_file was false,
49                // which means parse error.
50                return $this->xml === FALSE;
51        }
52       
53        ///////////////////// NEWS EXTRACTION
54        function load_news_data() {
55                $newsfile = $this->conf['webroot'].$this->conf['lang_path']."/".self::news_file;
56                $newsdir = dirname(realpath($newsfile));
57                // include path wird ignoriert wenn include relativ ist, was in der
58                // eingebundenen Datei der Fall ist
59                // set_include_path( get_include_path(). PATH_SEPARATOR . dirname($newsfile));
60                $pwd = getcwd(); chdir($newsdir);
61                include(self::news_file);
62                chdir($pwd);
63                return $neues_menu;
64        }
65
66        /**
67         * Liest das YAML-formatierte News-Menue aus der news.php-File der entsprechenden
68         * Sprache aus und erzeugt daraus ein HTML-Menue, welches als String zurueckgegeben
69         * wird.
70         * @param $host Instance of t29Host which can be used for link rewriting if given.
71         **/
72        function convert_news_data($host=null) {
73                require_once $this->conf['lib'].'/spyc.php';
74                $data = Spyc::YAMLLoad($this->load_news_data());
75                $fields = array('datum', 'titel',/* 'untertitel', 'bild'*/);
76
77                $news_ul_content = '';
78                foreach($data as $e) {
79                        if(!array_reduce(array_map(function($x) use ($fields,$e){ return isset($e[$x]); }, $fields),
80                                        function($a,$b){ return $a && $b;}, true)) {
81                                $li = "<li><a href='#'>Fehler in Formatierung!<em>Dieser Menüeintrag ist falsch formatiert</em></a></li>";
82                                $this->log->WARN("<h5>Neuigkeiten-Menü: Fehler in Formatierung</h5><p>Ein Eintrag im Neuigkeiten-Menü ist falsch formatiert. Ich erwarte zu jedem Menüeintrag die Felder ".implode(", ", $fields).". Eine der Angaben fehlt oder ist fehlerhaft formatiert: <pre>".var_export($e, true)."</pre>");
83                        } else {
84                                // Ehemals konnte die URL per "link: #August_2013" angegeben werden oder "link: /de/irgendwohin".
85                                // $url = ($e['link']{0} == '#' ? $this->conf['lang_path'].'/'.self::news_file : '').$e['link'];
86                                // Jetzt wird die URL automatisch aus dem Datum gebaut (slugify-artig)
87                                $url = $this->conf['lang_path'].'/'.self::news_file.'#'.str_replace(' ', '_', $e['datum']);
88                                if($host)
89                                        $url = $host->rewrite_link($url);
90
91                                // optionales Feld: Untertitel
92                                if(!isset($e['untertitel'])) $e['untertitel'] = '';
93
94                                // weiteres optionales Feld: Bildeinbindung
95                                $img = !isset($e['bild']) ? '' : "<img src='$e[bild]' style='max-width:64px; max-height:64px;'>";
96                                $li = "<li><a href='$url'>$img$e[titel]<span class='hidden'>: </span><em>$e[untertitel]</em></a></li>";
97                        }
98                        $news_ul_content .= "\t".$li."\n";
99                }
100
101                return $news_ul_content;
102        }
103       
104        ///////////////////// RETURN INFOS ABOUT SEITEN_ID LINK
105       
106        /**
107         * Find the corresponding XML node in the navigation tree for the link
108         * with given $seiten_id or the current given seiten_id in the configuration
109         * array.
110         * This method is used in get_link_navigation_class, etc. for resolving
111         * the XML element from the string. They can be used with the XML node, too,
112         * and this behaviour is passed throught by this method, so if you call
113         * this with an SimpleXMLElement as argument, it behaves like an identity
114         * function and just does nothing.
115         * (This is used in template.php for caching the found xml element and saving
116         * several xpath querys on get_* calls)
117         *
118         * @param $seiten_id Either a string, or nothing (defaults to conf) or SimpleXMLElement
119         * @returns SimpleXMLElement or null if link not found
120         **/
121        function get_link($seiten_id=false) {
122                if($this->xml_is_defective()) {
123                        return null;
124                }
125                if(!$seiten_id) $seiten_id = $this->conf['seiten_id'];
126                // convenience: If you found your link already.
127                if($seiten_id instanceof SimpleXMLElement) return $seiten_id;
128
129                $matches = $this->xml->xpath("//a[@seiten_id='$seiten_id']");
130                if($matches && count($matches)) {
131                        // strip the first one
132                        return $matches[0];
133                }
134                return null;
135        }
136       
137        /**
138         * Get navigation list membership (horizontal or side) of a link
139         * @see get_link for parameters
140         * @returns String of <nav> class where this link is affiliated to
141         **/
142        function get_link_navigation_class($seiten_id=false) {
143                $link = $this->get_link($seiten_id);
144                if(!$link) return null;
145               
146                // navigation list membership
147                $nav = $link->xpath("ancestor::nav");
148                return strval($nav[0]['class']); // cast SimpleXMLElement
149        }
150
151        /**
152         * Get list of parental ul classes (u2, u3, geraete, ...)
153         * @see get_link for parameters
154         * @returns array with individual class names as strings
155         **/
156        function get_link_ul_classes($seiten_id=false) {
157                $link = $this->get_link($seiten_id);
158                if(!$link) return array();
159               
160                // direct parental ul classes
161                $ul = $link->xpath("ancestor::ul");
162                $parent_ul = array_pop($ul);
163                return explode(' ',$parent_ul['class']);
164        }
165       
166        /**
167         * Extracts a list of (CSS) classes the link has,
168         * e.g. <a class="foo bar"> gives array("foo","basr").
169         *
170         * Caveat: This must be called before this class is destructed
171         * by print_menu! Otherwise it will return an empty array. This is
172         * actually bad design, print_menu destroyes the internal structure
173         * for storage efficiencey.
174         *
175         * @returns array or empty array in case of error
176         **/
177        function get_link_classes($seiten_id=false) {
178                $link = $this->get_link($seiten_id);
179                //print "link:"; var_dump($this->xml);
180                if(!$link) return array();
181                //var_dump($link); exit;
182                return isset($link['class']) ? explode(' ',$link['class']) : array();
183        }
184
185        ///////////////////// INTER LANGUAGE DETECTION
186        /**
187         * @param seiten_id Get interlanguage link for that seiten_id or default.
188         **/
189        function get_interlanguage_link($seiten_id=false) {
190                if(!$seiten_id) $seiten_id = $this->conf['seiten_id'];
191               
192                $interlinks = array(); // using a loop instead of mappings since php is stupid
193                foreach($this->conf['languages'] as $lang => $lconf) {
194                        $foreign_menu = new t29Menu(array(
195                                'webroot' => $this->conf['webroot'],
196                                'seiten_id' => $this->conf['seiten_id'],
197                                'languages' => $this->conf['languages'],
198                                'lang' => $lang,
199                                'lang_path' => $lconf[1],
200                        ));
201
202                        $link = $foreign_menu->get_link($seiten_id);
203                        $interlinks[$lang] = $link;
204                }
205               
206                return $interlinks;
207        }
208
209        // helper methods
210       
211        /** Check if a simplexml element has an attribute. Lightweight operation
212         *  over the DOM.
213         * @returns boolean
214         **/
215        public static function dom_has_attribute($simplexml_element, $attribute_name) {
216                $dom = dom_import_simplexml($simplexml_element); // is a fast operation
217                return $dom->hasAttribute($attribute_name);
218        }
219       
220        public static function dom_prepend_attribute($simplexml_element, $attribute_name, $content, $seperator='') {
221                if(!is_array($simplexml_element)) $simplexml_element = array($simplexml_element);
222                foreach($simplexml_element as $e)
223                        $e[$attribute_name] = $content . (self::dom_has_attribute($e, $attribute_name) ? ($seperator.$e[$attribute_name]) : '');
224        }
225       
226        public static function dom_append_attribute($simplexml_element, $attribute_name, $content, $seperator='') {
227                if(!is_array($simplexml_element)) $simplexml_element = array($simplexml_element);
228                foreach($simplexml_element as $e)
229                        $e[$attribute_name] = (self::dom_has_attribute($e, $attribute_name) ? ($e[$attribute_name].$seperator) : '') . $content;
230        }
231       
232        /**
233         * Appends a (CSS) class to a simplexml element, seperated by whitespace. Just an alias.
234         **/
235        public static function dom_add_class($simplexml_element, $value) {
236                self::dom_append_attribute($simplexml_element, 'class', $value, ' ');
237        }
238       
239        public static function dom_new_link($href, $label) {
240                return new SimpleXMLElement(sprintf('<a href="%s">%s</a>', $href, $label));
241        }
242
243
244        ///////////////////// MENU ACTIVE LINK DETECTION
245        /**
246         * print_menu is the central method in this class. It converts the $this->xml
247         * XML tree to valid HTML with all enrichments for appropriate CSS styling.
248         * It also removes all 'seiten_id' attributes.
249         * This method does *not* clone the structure, so this instance won't produce
250         * the same results any more after print_menu invocation! This especially will
251         * affect get_link().
252         *
253         * @arg $xpath_menu_selection  one of the horizontal_menu / sidebar_menu consts.
254         * @arg $host Instance of t29Host which can be used for link rewriting if given.
255         * @returns nothing, since the output is printed out
256         **/
257        function print_menu($xpath_menu_selection, $host=null) {
258                if($this->xml_is_defective()) {
259                        print "The Menu file is broken.";
260                        return false;
261                }
262                $seiten_id = $this->conf['seiten_id'];
263                $_ = $this->conf['msg']->get_shorthand_returner();
264               
265                // find wanted menu
266                $xml = $this->xml->xpath($xpath_menu_selection);
267                if(!$xml) {
268                        print "Menu <i>$xpath_menu_selection</i> not found!";
269                        return false;
270                }
271                $xml = $xml[0]; // just take the first result (should only one result be present)
272               
273                /*
274                // work on a deep copy of the data. Thus this method won't make the overall
275                // class useless.
276                $dom = dom_import_simplexml($xml);
277                $xml = simplexml_import_dom($dom->cloneNode(true));
278                */
279
280                // aktuelle Seite anmarkern und Hierarchie hochgehen
281                // (<ul><li>bla<ul><li>bla<ul><li>hierbin ich <- hochgehen.)
282                $current_a = $xml->xpath("//a[@seiten_id='$seiten_id']");
283                if(count($current_a)) {
284                        $current_li = $current_a[0]->xpath("parent::li");
285                        self::dom_add_class($current_li[0], 'current');
286                        self::dom_prepend_attribute($current_a, 'title', $_('nav-hierarchy-current'), ': ');
287
288                        $actives = $current_li[0]->xpath("ancestor-or-self::li");
289                        array_walk($actives, function($i) { t29Menu::dom_add_class($i, 'active'); });
290                       
291                        $ancestors = $current_li[0]->xpath("ancestor::li");
292                        foreach($ancestors as $i)
293                                t29Menu::dom_prepend_attribute($i->xpath("./a[1]"), 'title', $_('nav-hierarchy-ancestor'), ': ');
294                }
295
296                // Seiten-IDs (ungueltiges HTML) ummoddeln
297                $all_ids = $xml->xpath("//a[@seiten_id]");
298                foreach($all_ids as $a) {
299                        $a['id'] = "sidebar_link_".$a['seiten_id'];
300                        // umweg ueber DOM um Node zu loeschen
301                        $adom = dom_import_simplexml($a);
302                        $adom->removeAttribute('seiten_id');
303                }
304
305                // Geraete-Seiten entfernen
306                if($this->hide_geraete_seiten) {
307                        $geraete_uls = $xml->xpath("//ul[contains(@class, 'geraete')]");
308                        foreach($geraete_uls as $ul) {
309                                $uld = dom_import_simplexml($ul);
310                                $uld->parentNode->removeChild($uld);
311                        }
312                }
313               
314                // alle Links mittels t29Host umwandeln (idR .php-Endung entfernen),
315                // falls erwuenscht
316                if($host) {
317                        $links = $xml->xpath("//a[@href]");
318                        foreach($links as $a)
319                                $a['href'] = $host->rewrite_link($a['href']);
320                }
321       
322                if($xpath_menu_selection == self::horizontal_menu) {
323                        # inject news
324                        $news_ul_content = $this->convert_news_data($host);
325                        $magic_comment = '<!--# INSERT_NEWS #-->';
326                        $menu = $xml->ul->asXML();
327                        print str_replace($magic_comment, $news_ul_content, $menu);
328                } else {
329                        print $xml->ul->asXML();
330                }
331        }
332
333        ///////////////////// PAGE RELATIONS
334        /**
335         * Usage:
336         * foreach(get_page_relations() as $a) {
337         *    echo "Link $a going to $a[href]";
338         * }
339         *
340         * Hinweis:
341         * Wenn Element (etwa prev) nicht existent, nicht null zurueckgeben,
342         * sondern Element gar nicht zurueckgeben (aus hash loeschen).
343         *
344         * @param $seiten_id A seiten_id string or nothing for taking the current active string
345         * @returns an array(prev=>..., next=>...) or empty array, elements are SimpleXML a links
346         **/
347        function get_page_relations($seiten_id=false) {
348                if($this->xml_is_defective())
349                        return array(); // cannot construct relations due to bad XML file
350                if(!$seiten_id) $seiten_id = $this->conf['seiten_id'];
351               
352                $xml = $this->xml->xpath(self::sidebar_menu);
353                if(!$xml) { print "<i>Sidebar not found</i>"; return; }
354                $sidebar = $xml[0];
355               
356                $return = array();
357                $current_a = $sidebar->xpath("//a[@seiten_id='$seiten_id']");
358                if(count($current_a)) {
359                        // wenn aktuelle seite eine geraeteseite ist
360                        if(in_array('geraete', $this->get_link_ul_classes($seiten_id))) {
361                                //  pfad:                        a ->li->ul.geraete->li->li/a
362                                $geraetelink = $current_a[0]->xpath("../../../a");
363                                if(count($geraetelink))
364                                        $return['prev'] = $geraetelink[0];
365                                $return['next'] = null; // kein Link nach vorne
366                        } else {
367                                foreach(array(
368                                  "prev" => "preceding::a[@seiten_id]",
369                                  "next" => "following::a[@seiten_id]") as $rel => $xpath) {
370                                        $nodes = $current_a[0]->xpath($xpath);
371                                        foreach($rel == "prev" ? array_reverse($nodes) : $nodes as $link) {
372                                                $is_geraete = count($link->xpath("ancestor::ul[contains(@class, 'geraete')]"));
373                                                if($is_geraete) continue; // skip geraete links
374                                                $return[$rel] = $link;
375                                                break; // just take the first matching element
376                                        }
377                                } // end for prev next
378                        } // end if geraete
379                } else {
380                        // TODO PENDING: Der Fall tritt derzeit niemals ein, da das XML
381                        // sich dann doch irgendwie auf alles bezieht ($sidebar = alles) und
382                        // ueberall gesucht wird. Ist aber okay. oder?
383                        $return['start'] = t29Menu::dom_new_link('#', 'bla');
384                }
385               
386                // Linkliste aufarbeiten: Nullen rausschmeissen (nur deko) und
387                // Links *klonen*, denn sie werden durch print_menu sonst veraendert
388                // ("Übergeordnete Kategorie der aktuellen Seite" steht dann drin)
389                // und wir wollen sie unveraendert haben.
390                foreach($return as $key => $node) {
391                        if(!$node) {
392                                unset($return[$key]);
393                                continue;
394                        }
395                        $dn = dom_import_simplexml($node);
396                        $dnc = simplexml_import_dom($dn->cloneNode(true));
397                        $return[$key] = $dnc;
398                }
399               
400                return $return;
401        }
402
403} // class
Note: See TracBrowser for help on using the repository browser.
© 2008 - 2013 technikum29 • Sven Köppel • Some rights reserved
Powered by Trac
Expect where otherwise noted, content on this site is licensed under a Creative Commons 3.0 License