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

Last change on this file since 301 was 301, checked in by sven, 12 years ago

Zwei grosse Bugfixes die einige Backendänderungen nach sich zogen:

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