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

Last change on this file since 299 was 299, checked in by heribert, 12 years ago

Zahlreiche Bugfixes, vor allem an der englischen Seite und im Deutschen. Letzte Änderungen, bevor Homepage offiziell ihren Dienst antritt.

  • Property svn:keywords set to Id
File size: 10.5 KB
Line 
1<?php
2/**
3 * Needs conf:
4 *  webroot lang_path lang seiten_id languages
5 *
6 **/
7class t29Menu {
8        public $conf;
9        public $xml;
10
11        // jeweils relativ zum lang_path
12        const navigation_file = 'navigation.xml';
13        const news_file = 'news.php';
14
15        // xpath queries to the navigation elements
16        const horizontal_menu = '/html/nav[@class="horizontal"]';
17        const sidebar_menu = '/html/nav[@class="side"]';
18
19        function __construct($conf_array) {
20                $this->conf = $conf_array;
21               
22                // libxml: don't raise errors while parsing.
23                // will fetch them with libxml_get_errors later.
24                //libxml_use_internal_errors(true);
25
26                // load xml file
27                $this->xml = simplexml_load_file($this->conf['webroot'].$this->conf['lang_path'] . '/' . self::navigation_file);
28                if($this->xml_is_defective()) {
29                        trigger_error("Kann Navigationsdatei nicht verwenden, da das XML nicht sauber ist. Bitte reparieren!");
30                }
31        }
32
33        function xml_is_defective() {
34                // check if return value of simplexml_load_file was false,
35                // which means parse error.
36                return $this->xml === FALSE;
37        }
38       
39        ///////////////////// NEWS EXTRACTION
40        function load_news_data() {
41                $newsfile = $this->conf['webroot'].$this->conf['lang_path']."/".self::news_file;
42                $newsdir = dirname(realpath($newsfile));
43                // include path wird ignoriert wenn include relativ ist, was in der
44                // eingebundenen Datei der Fall ist
45                // set_include_path( get_include_path(). PATH_SEPARATOR . dirname($newsfile));
46                $pwd = getcwd(); chdir($newsdir);
47                include(self::news_file);
48                chdir($pwd);
49                return $neues_menu;
50        }
51
52        function convert_news_data() {
53                require $this->conf['lib'].'/spyc.php';
54                $data = Spyc::YAMLLoad($this->load_news_data());
55                $fields = array('titel', 'text', 'link', /*'bild'*/);
56
57                $news_ul_content = '';
58                foreach($data as $e) {
59                        if(!array_reduce(array_map(function($x) use ($fields,$e){ return isset($e[$x]); }, $fields),
60                                        function($a,$b){ return $a && $b;}, true)) {
61                                $li = "<li>Fehler in Formatierung!";
62                        } else {
63                                $url = ($e['link']{0} == '#' ? $this->conf['lang_path'].'/'.self::news_file : '').$e['link'];
64                                $li = "<li><a href='$url'>$e[titel]<span class='hidden'>: </span><em>$e[text]</em></a></li>";
65                        }
66                        $news_ul_content .= "\t".$li."\n";
67                }
68
69                return $news_ul_content;
70        }
71       
72        ///////////////////// RETURN INFOS ABOUT SEITEN_ID LINK
73       
74        /**
75         * Find the corresponding XML node in the navigation tree for the link
76         * with given $seiten_id or the current given seiten_id in the configuration
77         * array.
78         * This method is used in get_link_navigation_class, etc. for resolving
79         * the XML element from the string. They can be used with the XML node, too,
80         * and this behaviour is passed throught by this method, so if you call
81         * this with an SimpleXMLElement as argument, it behaves like an identity
82         * function and just does nothing.
83         * (This is used in template.php for caching the found xml element and saving
84         * several xpath querys on get_* calls)
85         *
86         * @param $seiten_id Either a string, or nothing (defaults to conf) or SimpleXMLElement
87         * @returns SimpleXMLElement or null if link not found
88         **/
89        function get_link($seiten_id=false) {
90                if($this->xml_is_defective()) {
91                        return null;
92                }
93                if(!$seiten_id) $seiten_id = $this->conf['seiten_id'];
94                // convenience: If you found your link already.
95                if($seiten_id instanceof SimpleXMLElement) return $seiten_id;
96
97                $matches = $this->xml->xpath("//a[@seiten_id='$seiten_id']");
98                if($matches && count($matches)) {
99                        // strip the first one
100                        return $matches[0];
101                }
102                return null;
103        }
104       
105        /**
106         * Get navigation list membership (horizontal or side) of a link
107         * @see get_link for parameters
108         * @returns String of <nav> class where this link is affiliated to
109         **/
110        function get_link_navigation_class($seiten_id=false) {
111                $link = $this->get_link($seiten_id);
112                if(!$link) return null;
113               
114                // navigation list membership
115                $nav = $link->xpath("ancestor::nav");
116                return strval($nav[0]['class']); // cast SimpleXMLElement
117        }
118
119        /**
120         * Get list of parental ul classes (u2, u3, geraete, ...)
121         * @see get_link for parameters
122         * @returns array with individual class names as strings
123         **/
124        function get_link_ul_classes($seiten_id=false) {
125                $link = $this->get_link($seiten_id);
126                if(!$link) return array();
127               
128                // direct parental ul classes
129                $ul = $link->xpath("ancestor::ul");
130                $parent_ul = array_pop($ul);
131                return explode(' ',$parent_ul['class']);
132        }
133
134        ///////////////////// INTER LANGUAGE DETECTION
135        /**
136         * @param seiten_id Get interlanguage link for that seiten_id or default.
137         **/
138        function get_interlanguage_link($seiten_id=false) {
139                if(!$seiten_id) $seiten_id = $this->conf['seiten_id'];
140               
141                $interlinks = array(); // using a loop instead of mappings since php is stupid
142                foreach($this->conf['languages'] as $lang => $lconf) {
143                        $foreign_menu = new t29Menu(array(
144                                'webroot' => $this->conf['webroot'],
145                                'seiten_id' => $this->conf['seiten_id'],
146                                'languages' => $this->conf['languages'],
147                                'lang' => $lang,
148                                'lang_path' => $lconf[1],
149                        ));
150
151                        $link = $foreign_menu->get_link($seiten_id);
152                        $interlinks[$lang] = $link;
153                }
154               
155                return $interlinks;
156        }
157
158        // helper methods
159       
160        /** Check if a simplexml element has an attribute. Lightweight operation
161         *  over the DOM.
162         * @returns boolean
163         **/
164        public static function dom_has_attribute($simplexml_element, $attribute_name) {
165                $dom = dom_import_simplexml($simplexml_element); // is a fast operation
166                return $dom->hasAttribute($attribute_name);
167        }
168       
169        public static function dom_prepend_attribute($simplexml_element, $attribute_name, $content, $seperator='') {
170                if(!is_array($simplexml_element)) $simplexml_element = array($simplexml_element);
171                foreach($simplexml_element as $e)
172                        $e[$attribute_name] = $content . (self::dom_has_attribute($e, $attribute_name) ? ($seperator.$e[$attribute_name]) : '');
173        }
174       
175        public static function dom_append_attribute($simplexml_element, $attribute_name, $content, $seperator='') {
176                if(!is_array($simplexml_element)) $simplexml_element = array($simplexml_element);
177                foreach($simplexml_element as $e)
178                        $e[$attribute_name] = (self::dom_has_attribute($e, $attribute_name) ? ($e[$attribute_name].$seperator) : '') . $content;
179        }
180       
181        /**
182         * Appends a (CSS) class to a simplexml element, seperated by whitespace. Just an alias.
183         **/
184        public static function dom_add_class($simplexml_element, $value) {
185                self::dom_append_attribute($simplexml_element, 'class', $value, ' ');
186        }
187       
188        public static function dom_new_link($href, $label) {
189                return new SimpleXMLElement(sprintf('<a href="%s">%s</a>', $href, $label));
190        }
191
192
193        ///////////////////// MENU ACTIVE LINK DETECTION
194        /**
195         * @arg $xpath_menu_selection  one of the horizontal_menu / sidebar_menu consts.
196         **/
197        function print_menu($xpath_menu_selection) {
198                if($this->xml_is_defective()) {
199                        print "The Menu file is broken.";
200                        return false;
201                }
202                $seiten_id = $this->conf['seiten_id'];
203
204                // find wanted menu
205                $xml = $this->xml->xpath($xpath_menu_selection);
206                if(!$xml) {
207                        print "Menu <i>$xpath_menu_selection</i> not found!";
208                        return false;
209                }
210                $xml = $xml[0]; // just take the first result (should only one result be present)
211
212                // aktuelle Seite anmarkern und Hierarchie hochgehen
213                // (<ul><li>bla<ul><li>bla<ul><li>hierbin ich <- hochgehen.)
214                $current_a = $xml->xpath("//a[@seiten_id='$seiten_id']");
215                if(count($current_a)) {
216                        $current_li = $current_a[0]->xpath("parent::li");
217                        self::dom_add_class($current_li[0], 'current');
218                        self::dom_prepend_attribute($current_a, 'title', 'Aktuelle Seite', ': ');
219
220                        $actives = $current_li[0]->xpath("ancestor-or-self::li");
221                        array_walk($actives, function($i) { t29Menu::dom_add_class($i, 'active'); });
222                       
223                        $ancestors = $current_li[0]->xpath("ancestor::li");
224                        array_walk($ancestors, function($i) {
225                                t29Menu::dom_prepend_attribute($i->xpath("./a[1]"), 'title', 'Übergeordnete Kategorie der aktuellen Seite' , ': ');
226                        });
227                }
228
229                // Seiten-IDs (ungueltiges HTML) ummoddeln
230                $all_ids = $xml->xpath("//a[@seiten_id]");
231                foreach($all_ids as $a) {
232                        $a['id'] = "sidebar_link_".$a['seiten_id'];
233                        // umweg ueber DOM um Node zu loeschen
234                        $adom = dom_import_simplexml($a);
235                        $adom->removeAttribute('seiten_id');
236                }
237
238                // Geraete-Seiten entfernen
239                $geraete_uls = $xml->xpath("//ul[contains(@class, 'geraete')]");
240                foreach($geraete_uls as $ul) {
241                        $uld = dom_import_simplexml($ul);
242                        $uld->parentNode->removeChild($uld);
243                }
244       
245       
246                if($xpath_menu_selection == self::horizontal_menu) {
247                        # inject news
248                        $news_ul_content = $this->convert_news_data();
249                        $magic_comment = '<!--# INSERT_NEWS #-->';
250                        $menu = $xml->ul->asXML();
251                        print str_replace($magic_comment, $news_ul_content, $menu);
252                } else {
253                        print $xml->ul->asXML();
254                }
255        }
256
257        ///////////////////// PAGE RELATIONS
258        /**
259         * Usage:
260         * foreach(get_page_relations() as $a) {
261         *    echo "Link $a going to $a[href]";
262         * }
263         *
264         * Hinweis:
265         * Wenn Element (etwa prev) nicht existent, nicht null zurueckgeben,
266         * sondern Element gar nicht zurueckgeben (aus hash loeschen).
267         *
268         * @param $seiten_id A seiten_id string or nothing for taking the current active string
269         * @returns an array(prev=>..., next=>...) or empty array, elements are SimpleXML a links
270         **/
271        function get_page_relations($seiten_id=false) {
272                if($this->xml_is_defective())
273                        return array(); // cannot construct relations due to bad XML file
274                if(!$seiten_id) $seiten_id = $this->conf['seiten_id'];
275               
276                $xml = $this->xml->xpath(self::sidebar_menu);
277                if(!$xml) { print "<i>Sidebar not found</i>"; return; }
278                $sidebar = $xml[0];
279               
280                $return = array();
281                $current_a = $sidebar->xpath("//a[@seiten_id='$seiten_id']");
282                if(count($current_a)) {
283                        // wenn aktuelle seite eine geraeteseite ist
284                        if(in_array('geraete', $this->get_link_ul_classes($seiten_id))) {
285                                //$return['next'] = null; // kein Link nach vorne
286                                //$return['prev'] = null; // TODO: Da muss der richtige Link auf die Seite, die auf diese Extraseite verweist.
287                        } else {
288                                foreach(array(
289                                  "prev" => "preceding::a[@seiten_id]",
290                                  "next" => "following::a[@seiten_id]") as $rel => $xpath) {
291                                        $nodes = $current_a[0]->xpath($xpath);
292                                        foreach($rel == "prev" ? array_reverse($nodes) : $nodes as $link) {
293                                                $is_geraete = count($link->xpath("ancestor::ul[contains(@class, 'geraete')]"));
294                                                if($is_geraete) continue; // skip geraete links
295                                                $return[$rel] = $link;
296                                                break; // just take the first matching element
297                                        }
298                                } // end for prev next
299                        } // end if geraete
300                } else {
301                        // TODO PENDING: Der Fall tritt derzeit niemals ein, da das XML
302                        // sich dann doch irgendwie auf alles bezieht ($sidebar = alles) und
303                        // ueberall gesucht wird. Ist aber okay. oder?
304                        $return['start'] = t29Menu::dom_new_link('#', 'bla');
305                }
306                return $return;
307        }
308
309} // 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