Project

General

Profile

1
<?php
2
/**
3
 *
4
 * @category        module
5
 * @package         show_menu2
6
 * @author          WebsiteBaker Project
7
 * @copyright       2004-2009, Ryan Djurovich
8
 * @copyright       2009-2010, Website Baker Org. e.V.
9
 * @link			http://www.websitebaker2.org/
10
 * @license         http://www.gnu.org/licenses/gpl.html
11
 * @platform        WebsiteBaker 2.7.0 | 2.8.x
12
 * @requirements    PHP 4.4.9 and higher
13
 * @version         $Id: include.php 1351 2010-12-21 04:31:29Z Luisehahne $
14
 * @filesource		$HeadURL: svn://isteam.dynxs.de/wb-archiv/branches/2.8.x/wb/modules/show_menu2/include.php $
15
 * @lastmodified    $Date: 2010-12-21 05:31:29 +0100 (Tue, 21 Dec 2010) $
16
 *
17
 */
18

    
19
define('SM2_ROOT',          -1000);
20
define('SM2_CURR',          -2000);
21
define('SM2_ALLMENU',          -1);
22
define('SM2_START',          1000);
23
define('SM2_MAX',            2000);
24
define('SM2_ALL',          0x0001); // bit 0 (group 1) (Note: also used for max level!)
25
define('SM2_TRIM',         0x0002); // bit 1 (group 1)
26
define('SM2_CRUMB',        0x0004); // bit 2 (group 1)
27
define('SM2_SIBLING',      0x0008); // bit 3 (group 1)
28
define('SM2_NUMCLASS',     0x0010); // bit 4
29
define('SM2_ALLINFO',      0x0020); // bit 5
30
define('SM2_NOCACHE',      0x0040); // bit 6
31
define('SM2_PRETTY',       0x0080); // bit 7
32
define('SM2_ESCAPE',       0x0100); // bit 8
33
define('SM2_NOESCAPE',          0); // NOOP, unnecessary with WB 2.6.7+
34
define('SM2_BUFFER',       0x0200); // bit 9
35
define('SM2_CURRTREE',     0x0400); // bit 10
36
define('SM2_SHOWHIDDEN',   0x0800); // bit 11
37
define('SM2_XHTML_STRICT', 0x1000); // bit 12
38
define('SM2_NO_TITLE',     0x1001); // bit 13
39

    
40
define('_SM2_GROUP_1',  0x000F); // exactly one flag from group 1 is required
41

    
42

    
43
// Implement support for page_menu and show_menu using show_menu2. If you remove
44
// the comments characters from the beginning of the following include, all menu
45
// functions in Website Baker will be implemented using show_menu2. While it is
46
// commented out, the original WB functions will be used.
47
//include('legacy.php');
48

    
49
// This class is the default menu formatter for sm2. If desired, you can 
50
// create your own formatter class and pass the object into show_menu2 
51
// as $aItemFormat.
52
define('SM2_CONDITIONAL','if\s*\(([^\)]+)\)\s*{([^}]*)}\s*(?:else\s*{([^}]*)}\s*)?');
53
define('SM2_COND_TERM','\s*(\w+)\s*(<|<=|==|=|=>|>|!=)\s*([\w\-]+)\s*');
54
class SM2_Formatter
55
{
56
    var $output;
57
    var $flags;
58
    var $itemOpen;
59
    var $itemClose;
60
    var $menuOpen;
61
    var $menuClose;
62
    var $topItemOpen;
63
    var $topMenuOpen;
64
    
65
    var $isFirst;
66
    var $page;
67
    var $url;
68
    var $currSib;
69
    var $sibCount;
70
    var $currClass;
71
    var $prettyLevel;
72

    
73
    // output the data
74
    function output($aString) {
75
        if ($this->flags & SM2_BUFFER) {
76
            $this->output .= $aString;
77
        }
78
        else {
79
            echo $aString;
80
        }
81
    }
82
    
83
    // set the default values for all of our formatting items
84
    function set($aFlags, $aItemOpen, $aItemClose, $aMenuOpen, $aMenuClose, $aTopItemOpen, $aTopMenuOpen) {
85
        $this->flags        = $aFlags;
86
        $this->itemOpen     = is_string($aItemOpen)    ? $aItemOpen    : '[li][a][menu_title]</a>';
87
        $this->itemClose    = is_string($aItemClose)   ? $aItemClose   : '</li>';
88
        $this->menuOpen     = is_string($aMenuOpen)    ? $aMenuOpen    : '[ul]';
89
        $this->menuClose    = is_string($aMenuClose)   ? $aMenuClose   : '</ul>';
90
        $this->topItemOpen  = is_string($aTopItemOpen) ? $aTopItemOpen : $this->itemOpen;
91
        $this->topMenuOpen  = is_string($aTopMenuOpen) ? $aTopMenuOpen : $this->menuOpen;
92
    }
93

    
94
    // initialize the state of the formatter before anything is output
95
    function initialize() {
96
        $this->output = '';
97
        $this->prettyLevel = 0;
98
        if ($this->flags & SM2_PRETTY) {
99
            $this->output("\n<!-- show_menu2 -->");
100
        }
101
    }
102

    
103
    // start a menu     
104
    function startList(&$aPage, &$aUrl) {
105
        $currClass = '';
106
        $currItem = $this->menuOpen;
107
        
108
        // use the top level menu open if this is the first menu
109
        if ($this->topMenuOpen) {
110
            $currItem = $this->topMenuOpen;
111
            $currClass .= ' menu-top';
112
            $this->topMenuOpen = false;
113
        }
114
        
115
        // add the numbered menu class only if requested
116
        if (($this->flags & SM2_NUMCLASS) == SM2_NUMCLASS) {
117
            $currClass .= ' menu-'.$aPage['level'];
118
        }
119
        
120
        $this->prettyLevel += 1;
121
        
122
        // replace all keywords in the output
123
        if ($this->flags & SM2_PRETTY) {
124
            $this->output("\n".str_repeat(' ',$this->prettyLevel).
125
                $this->format($aPage, $aUrl, $currItem, $currClass));
126
        }
127
        else {
128
            $this->output($this->format($aPage, $aUrl, $currItem, $currClass));
129
        }
130
        
131
        $this->prettyLevel += 3;
132
    }
133
    
134
    // start an item within the menu
135
    function startItem(&$aPage, &$aUrl, $aCurrSib, $aSibCount) {
136
        // generate our class list
137
        $currClass = '';
138
        if (($this->flags & SM2_NUMCLASS) == SM2_NUMCLASS) {
139
            $currClass .= ' menu-'.$aPage['level'];
140
        }
141
        if (array_key_exists('sm2_has_child', $aPage)) {
142
            // not set if false, so existence = true
143
            $currClass .= ' menu-expand';
144
        }
145
        if (array_key_exists('sm2_is_curr', $aPage)) { 
146
            $currClass .= ' menu-current';
147
        }
148
        elseif (array_key_exists('sm2_is_parent', $aPage)) { 
149
            // not set if false, so existence = true
150
            $currClass .= ' menu-parent';
151
        }
152
        elseif (array_key_exists('sm2_is_sibling', $aPage)) {
153
            // not set if false, so existence = true
154
            $currClass .= ' menu-sibling';
155
        }
156
        elseif (array_key_exists('sm2_child_level',$aPage)) {
157
            // not set if not a child
158
            $currClass .= ' menu-child';
159
            if (($this->flags & SM2_NUMCLASS) == SM2_NUMCLASS) {
160
                $currClass .= ' menu-child-'.($aPage['sm2_child_level']-1);
161
            }
162
        }
163
        if ($aCurrSib == 1) {
164
            $currClass .= ' menu-first';
165
        }
166
        if ($aCurrSib == $aSibCount) {
167
            $currClass .= ' menu-last';
168
        }
169

    
170
        // use the top level item if this is the first item
171
        $currItem = $this->itemOpen;
172
        if ($this->topItemOpen) {
173
            $currItem = $this->topItemOpen;
174
            $this->topItemOpen = false;
175
        }
176

    
177
        // replace all keywords in the output
178
        if ($this->flags & SM2_PRETTY) {
179
            $this->output("\n".str_repeat(' ',$this->prettyLevel));
180
        }
181
        $this->output($this->format($aPage, $aUrl, $currItem, $currClass, $aCurrSib, $aSibCount));
182
    }
183
    
184
    // find and replace all keywords, setting the state variables first
185
    function format(&$aPage, &$aUrl, &$aCurrItem, &$aCurrClass, 
186
        $aCurrSib = 0, $aSibCount = 0) 
187
    {
188
        $this->page      = &$aPage;
189
        $this->url       = &$aUrl;
190
        $this->currClass = trim($aCurrClass);
191
        $this->currSib   = $aCurrSib;
192
        $this->sibCount  = $aSibCount;
193
        
194
        $item = $this->format2($aCurrItem);
195
        
196
        unset($this->page);
197
        unset($this->url);
198
        unset($this->currClass);
199
        
200
        return $item;
201
    }
202
    
203
    // find and replace all keywords
204
    function format2(&$aCurrItem) {
205
        if (!is_string($aCurrItem)) return '';
206
        return preg_replace(
207
            '@\[('.
208
                'a|ac|/a|li|/li|ul|/ul|menu_title|menu_icon_0|menu_icon_1|'.
209
				'page_title|page_icon|url|target|page_id|'.
210
                'parent|level|sib|sibCount|class|description|keywords|'.
211
                SM2_CONDITIONAL.
212
            ')\]@e', 
213
            '$this->replace("\1")', $aCurrItem);
214
    }
215
    
216
    // replace the keywords
217
    function replace($aMatch) {
218
        $retval = '['.$aMatch.'=UNKNOWN]';
219
		$retval_1 = '';
220
        switch ($aMatch) {
221
        case 'a':
222
            $retval_1 = '<a href="'.$this->url.'"';
223
		case 'ac':
224
            $retval = '<a href="'.$this->url.'" class="'.$this->currClass.'"';
225
			$retval = ($retval_1 == '') ? $retval : $retval_1;
226
			if(!($this->flags & SM2_XHTML_STRICT))
227
			{
228
				$retval .= ' target="'.$this->page['target'].'"';
229
			}
230
			$retval .= ' title="'.(($this->flags & SM2_NO_TITLE) ? '&nbsp;' : $this->page['page_title']).'">';
231
			break;
232
        case '/a':
233
            $retval = '</a>'; break;
234
        case 'li':
235
            $retval = '<li class="'.$this->currClass.'">'; break;
236
        case '/li':
237
            $retval = '</li>'; break;
238
        case 'ul':
239
            $retval = '<ul class="'.$this->currClass.'">'; break;
240
        case '/ul':
241
            $retval = '</ul>'; break;
242
        case 'url':
243
            $retval = $this->url; break;
244
        case 'sib':
245
            $retval = $this->currSib; break;
246
        case 'sibCount':
247
            $retval = $this->sibCount; break;
248
        case 'class':
249
            $retval = $this->currClass; break;
250
        default:
251
            if (array_key_exists($aMatch, $this->page)) {
252
                if ($this->flags & SM2_ESCAPE) {
253
                    $retval = htmlspecialchars($this->page[$aMatch], ENT_QUOTES);
254
                }
255
                else {
256
                    $retval = $this->page[$aMatch];
257
                }
258
            }
259
            if (preg_match('/'.SM2_CONDITIONAL.'/', $aMatch, $rgMatches)) {
260
                $retval = $this->replaceIf($rgMatches[1], $rgMatches[2], $rgMatches[3]);
261
            }
262
        }
263
        return $retval;
264
    }
265
    
266
    // conditional replacement
267
    function replaceIf(&$aExpression, &$aIfValue, &$aElseValue) {
268
        // evaluate all of the tests in the conditional (we don't do short-circuit
269
        // evaluation) and replace the string test with the boolean result
270
        $rgTests = preg_split('/(\|\||\&\&)/', $aExpression, -1, PREG_SPLIT_DELIM_CAPTURE);
271
        for ($n = 0; $n < count($rgTests); $n += 2) {
272
            if (preg_match('/'.SM2_COND_TERM.'/', $rgTests[$n], $rgMatches)) {
273
                $rgTests[$n] = $this->ifTest($rgMatches[1], $rgMatches[2], $rgMatches[3]);
274
            }
275
            else {
276
                @error_logs("show_menu2 error: conditional expression is invalid!");
277
                $rgTests[$n] = false;
278
            }
279
        }
280

    
281
        // combine all test results for a final result
282
        $ok = $rgTests[0];
283
        for ($n = 1; $n+1 < count($rgTests); $n += 2) {
284
            if ($rgTests[$n] == '||') {
285
                $ok = $ok || $rgTests[$n+1];
286
            }
287
            else {
288
                $ok = $ok && $rgTests[$n+1];
289
            }
290
        }
291
        
292
        // return the formatted expression if the test succeeded
293
        return $ok ? $this->format2($aIfValue) : $this->format2($aElseValue);
294
    }
295

    
296
    // conditional test
297
    function ifTest(&$aKey, &$aOperator, &$aValue) {
298
        global $wb;
299
        
300
        // find the correct operand
301
        $operand = false;
302
        switch($aKey) {
303
        case 'class':
304
            // we need to wrap the class names in spaces so we can test for a unique
305
            // class name that will not match prefixes or suffixes. Same must be done
306
            // for the value we are testing.
307
            $operand = " $this->currClass "; 
308
            break;
309
		case 'target':
310
			$operand = $this->page['target'];
311
			break;
312
        case 'sib':
313
            $operand = $this->currSib;
314
            if ($aValue == 'sibCount') {
315
                $aValue = $this->sibCount;
316
            }
317
            break;
318
        case 'sibCount':
319
            $operand = $this->sibCount;
320
            break;
321
        case 'level':
322
            $operand = $this->page['level'];
323
            switch ($aValue) {
324
            case 'root':    $aValue = 0; break;
325
            case 'granny':  $aValue = $wb->page['level']-2; break;
326
            case 'parent':  $aValue = $wb->page['level']-1; break;
327
            case 'current': $aValue = $wb->page['level'];   break;
328
            case 'child':   $aValue = $wb->page['level']+1; break;
329
            }
330
            if ($aValue < 0) $aValue = 0;
331
            break;
332
        case 'id':
333
            $operand = $this->page['page_id'];
334
            switch ($aValue) {
335
            case 'parent':  $aValue = $wb->page['parent'];  break;
336
            case 'current': $aValue = $wb->page['page_id']; break;
337
            }
338
            break;
339
        default:
340
            return '';
341
        }
342

    
343
        // do the test        
344
        $ok = false;
345
        switch ($aOperator) { 
346
        case '<':
347
            $ok = ($operand < $aValue); 
348
            break;
349
        case '<=':
350
            $ok = ($operand <= $aValue); 
351
            break;
352
        case '=':
353
        case '==':
354
        case '!=':
355
            if ($aKey == 'class') {
356
                $ok = strstr($operand, " $aValue ") !== FALSE;
357
            }
358
            else {
359
                $ok = ($operand == $aValue); 
360
            }
361
            if ($aOperator == '!=') {
362
                $ok = !$ok;
363
            }
364
            break;
365
        case '>=':
366
            $ok = ($operand >= $aValue); 
367
        case '>':
368
            $ok = ($operand > $aValue); 
369
        }
370
        
371
        return $ok;
372
    }
373
    
374
    // finish the current menu item
375
    function finishItem() {
376
        if ($this->flags & SM2_PRETTY) {
377
            $this->output(str_repeat(' ',$this->prettyLevel).$this->itemClose);
378
        }
379
        else {
380
            $this->output($this->itemClose);
381
        }
382
    }
383

    
384
    // finish the current menu
385
    function finishList() {
386
        $this->prettyLevel -= 3;
387
        
388
        if ($this->flags & SM2_PRETTY) {
389
            $this->output("\n".str_repeat(' ',$this->prettyLevel).$this->menuClose."\n");
390
        }
391
        else {
392
            $this->output($this->menuClose);
393
        }
394
        
395
        $this->prettyLevel -= 1;
396
    }
397
    
398
    // cleanup the state of the formatter after everything has been output
399
    function finalize() {
400
        if ($this->flags & SM2_PRETTY) {
401
            $this->output("\n");
402
        }
403
    }
404

    
405
    // return the output
406
    function getOutput() {
407
        return $this->output;
408
    }
409
};
410

    
411
function error_logs($error_str)
412
{
413
                $log_error = true;
414
                if ( ! function_exists('error_log') )
415
                        $log_error = false;
416

    
417
                $log_file = @ini_get('error_log');
418
                if ( !empty($log_file) && ('syslog' != $log_file) && !@is_writable($log_file) )
419
                        $log_error = false;
420

    
421
                if ( $log_error )
422
                        @error_log($error_str, 0);
423
}
424

    
425
function show_menu2(
426
    $aMenu          = 0,
427
    $aStart         = SM2_ROOT,
428
    $aMaxLevel      = -1999, // SM2_CURR+1
429
    $aOptions       = SM2_TRIM,
430
    $aItemOpen      = false,
431
    $aItemClose     = false,
432
    $aMenuOpen      = false,
433
    $aMenuClose     = false,
434
    $aTopItemOpen   = false,
435
    $aTopMenuOpen   = false
436
    )
437
{
438
    global $wb;
439

    
440
    // extract the flags and set $aOptions to an array
441
    $flags = 0;
442
    if (is_int($aOptions)) {
443
        $flags = $aOptions;
444
        $aOptions = array();
445
    }
446
    else if (isset($aOptions['flags'])) {
447
        $flags = $aOptions['flags'];
448
    }
449
    else {
450
        $flags = SM2_TRIM;
451
        $aOptions = array();
452
        @error_logs('show_menu2 error: $aOptions is invalid. No flags supplied!');
453
    }
454
    
455
    // ensure we have our group 1 flag, we don't check for the "exactly 1" part, but
456
    // we do ensure that they provide at least one.
457
    if (0 == ($flags & _SM2_GROUP_1)) {
458
        @error_logs('show_menu2 error: $aOptions is invalid. No flags from group 1 supplied!');
459
        $flags |= SM2_TRIM; // default to TRIM
460
    }
461
    
462
    // search page results don't have any of the page data loaded by WB, so we load it 
463
    // ourselves using the referrer ID as the current page
464
    $CURR_PAGE_ID = defined('REFERRER_ID') ? REFERRER_ID : PAGE_ID;
465
    if (count($wb->page) == 0 && defined('REFERRER_ID') && REFERRER_ID > 0) {
466
        global $database;
467
        $sql = 'SELECT * FROM `'.TABLE_PREFIX.'pages` WHERE `page_id` = '.REFERRER_ID.'';
468
        $result = $database->query($sql);
469
        if ($result->numRows() == 1) {
470
            $wb->page = $result->fetchRow();
471
        }
472
        unset($result);
473
    }
474
    
475
    // fix up the menu number to default to the menu number
476
    // of the current page if no menu has been supplied
477
    if ($aMenu == 0) {
478
        $aMenu = $wb->page['menu'] == '' ? 1 : $wb->page['menu'];
479
    } 
480

    
481
    // Set some of the $wb->page[] settings to defaults if not set
482
    $pageLevel  = $wb->page['level']  == '' ? 0 : $wb->page['level'];
483
    $pageParent = $wb->page['parent'] == '' ? 0 : $wb->page['parent'];
484
    
485
    // adjust the start level and start page ID as necessary to
486
    // handle the special values that can be passed in as $aStart
487
    $aStartLevel = 0;
488
    if ($aStart < SM2_ROOT) {   // SM2_CURR+N
489
        if ($aStart == SM2_CURR) {
490
            $aStartLevel = $pageLevel;
491
            $aStart = $pageParent;
492
        }
493
        else {
494
            $aStartLevel = $pageLevel + $aStart - SM2_CURR;
495
            $aStart = $CURR_PAGE_ID; 
496
        }
497
    }
498
    elseif ($aStart < 0) {   // SM2_ROOT+N
499
        $aStartLevel = $aStart - SM2_ROOT;
500
        $aStart = 0;
501
    }
502

    
503
    // we get the menu data once and store it in a global variable. This allows 
504
    // multiple calls to show_menu2 in a single page with only a single call to 
505
    // the database. If this variable exists, then we have already retrieved all
506
    // of the information and processed it, so we don't need to do it again.
507
    if (($flags & SM2_NOCACHE) != 0
508
        || !array_key_exists('show_menu2_data', $GLOBALS)
509
        || !array_key_exists($aMenu, $GLOBALS['show_menu2_data'])) 
510
    {
511
        global $database;
512

    
513
        // create an array of all parents of the current page. As the page_trail
514
        // doesn't include the theoretical root element 0, we add it ourselves.
515
        $rgCurrParents = explode(",", '0,'.$wb->page['page_trail']);
516
        array_pop($rgCurrParents); // remove the current page
517
        $rgParent = array();
518

    
519
        // if the caller wants all menus gathered together (e.g. for a sitemap)
520
        // then we don't limit our SQL query
521
        $menuLimitSql = ' AND `menu`='.$aMenu;
522
        if ($aMenu == SM2_ALLMENU) {
523
            $menuLimitSql = '';
524
        }
525

    
526
        // we only load the description and keywords if we have been told to,
527
        // this cuts the memory load for pages that don't use them. Note that if
528
        // we haven't been told to load these fields the *FIRST TIME* show_menu2
529
        // is called (i.e. where the database is loaded) then the info won't
530
        // exist anyhow.
531
        $fields  = '`parent`,`page_id`,`menu_title`,`page_title`,`link`,`target`,';
532
		$fields .= '`level`,`visibility`,`viewing_groups`';
533
        if (version_compare(WB_VERSION, '2.7', '>=')) { // WB 2.7+
534
            $fields .= ',`viewing_users`';
535
        }
536
		if(version_compare(WB_VERSION, '2.9.0', '>=')) {
537
            $fields .= ',`menu_icon_0`,`menu_icon_1`,`page_icon`';
538
		}
539
        if ($flags & SM2_ALLINFO) {
540
            $fields = '*';
541
        }
542

    
543
        // we request all matching rows from the database for the menu that we
544
        // are about to create it is cheaper for us to get everything we need
545
        // from the database once and create the menu from memory then make 
546
        // multiple calls to the database. 
547
        $sql  = 'SELECT '.$fields.' FROM `'.TABLE_PREFIX.'pages` ';
548
		$sql .= 'WHERE '.$wb->extra_where_sql.' '.$menuLimitSql.' ';
549
		$sql .= 'ORDER BY `level` ASC, `position` ASC';
550
        $sql = str_replace('hidden', 'IGNOREME', $sql); // we want the hidden pages
551
        $oRowset = $database->query($sql);
552
        if (is_object($oRowset) && $oRowset->numRows() > 0) {
553
            // create an in memory array of the database data based on the item's parent. 
554
            // The array stores all elements in the correct display order.
555
            while ($page = $oRowset->fetchRow()) {
556
                // ignore all pages that the current user is not permitted to view
557
                if(version_compare(WB_VERSION, '2.7', '>=')) { // WB >= 2.7
558
                    // 1. hidden pages aren't shown unless they are on the current page
559
                    if ($page['visibility'] == 'hidden') {
560
                        $page['sm2_hide'] = true;
561
                    }
562
                    
563
                    // 2. all pages with no active sections (unless it is the top page) are ignored
564
                    else if (!$wb->page_is_active($page) && $page['link'] != $wb->default_link && !INTRO_PAGE) {
565
                        continue;
566
                    }
567

    
568
                    // 3. all pages not visible to this user (unless always visible to registered users) are ignored
569
                    else if (!$wb->page_is_visible($page) && $page['visibility'] != 'registered') {
570
                        continue;
571
                    }
572
                }
573

    
574
                // ensure that we have an array entry in the table to add this to
575
                $idx = $page['parent'];
576
                if (!array_key_exists($idx, $rgParent)) {
577
                    $rgParent[$idx] = array();
578
                }
579

    
580
                // mark our current page as being on the current path
581
                if ($page['page_id'] == $CURR_PAGE_ID) {
582
                    $page['sm2_is_curr'] = true;
583
                    $page['sm2_on_curr_path'] = true;
584
                    if ($flags & SM2_SHOWHIDDEN) 
585
					{ 
586
                        // show hidden pages if active and SHOWHIDDEN flag supplied
587
                        unset($page['sm2_hide']); 
588
                    }
589
                }
590

    
591
                // mark parents of the current page as such
592
                if (in_array($page['page_id'], $rgCurrParents)) {
593
                    $page['sm2_is_parent'] = true;
594
                    $page['sm2_on_curr_path'] = true;
595
                    if ($flags & SM2_SHOWHIDDEN) 
596
					{
597
                        // show hidden pages if active and SHOWHIDDEN flag supplied
598
						unset($page['sm2_hide']); // don't hide a parent page                
599
                    }
600
                }
601
                
602
                // add the entry to the array                
603
                $rgParent[$idx][] = $page;
604
            }
605
        }    
606
        unset($oRowset);
607

    
608
        // mark all elements that are siblings of any element on the current path
609
        foreach ($rgCurrParents as $x) {
610
            if (array_key_exists($x, $rgParent)) {
611
                foreach (array_keys($rgParent[$x]) as $y) {
612
                    $mark =& $rgParent[$x][$y];
613
                    $mark['sm2_path_sibling'] = true;
614
                    unset($mark);
615
                }
616
            }
617
        }
618

    
619
        // mark all elements that have children and are siblings of the current page
620
        $parentId = $pageParent;
621
        foreach (array_keys($rgParent) as $x) {
622
            $childSet =& $rgParent[$x];
623
            foreach (array_keys($childSet) as $y) {
624
                $mark =& $childSet[$y];
625
                if (array_key_exists($mark['page_id'], $rgParent)) {
626
                    $mark['sm2_has_child'] = true;
627
                }
628
                if ($mark['parent'] == $parentId && $mark['page_id'] != $CURR_PAGE_ID) {
629
                    $mark['sm2_is_sibling'] = true;
630
                }
631
                unset($mark);
632
            }
633
            unset($childSet);
634
        }
635
        
636
        // mark all children of the current page. We don't do this when 
637
        // $CURR_PAGE_ID is 0, as 0 is the parent of everything. 
638
        // $CURR_PAGE_ID == 0 occurs on special pages like search results
639
        // when no referrer is available.s
640
        if ($CURR_PAGE_ID != 0) {
641
            sm2_mark_children($rgParent, $CURR_PAGE_ID, 1);
642
        }
643
        
644
        // store the complete processed menu data as a global. We don't 
645
        // need to read this from the database anymore regardless of how 
646
        // many menus are displayed on the same page.
647
        if (!array_key_exists('show_menu2_data', $GLOBALS)) {
648
            $GLOBALS['show_menu2_data'] = array();
649
        }
650
        $GLOBALS['show_menu2_data'][$aMenu] =& $rgParent;
651
        unset($rgParent);
652
    }
653

    
654
    // adjust $aMaxLevel to the level number of the final level that 
655
    // will be displayed. That is, we display all levels <= aMaxLevel.
656
    if ($aMaxLevel == SM2_ALL) {
657
        $aMaxLevel = 1000;
658
    }
659
    elseif ($aMaxLevel < 0) {   // SM2_CURR+N
660
        $aMaxLevel += $pageLevel - SM2_CURR;
661
    }
662
    elseif ($aMaxLevel >= SM2_MAX) { // SM2_MAX+N
663
        $aMaxLevel += $aStartLevel - SM2_MAX;
664
        if ($aMaxLevel > $pageLevel) {
665
            $aMaxLevel = $pageLevel;
666
        }
667
    }
668
    else {  // SM2_START+N
669
        $aMaxLevel += $aStartLevel - SM2_START;
670
    }
671

    
672
    // generate the menu
673
    $retval = false;
674
    if (array_key_exists($aStart, $GLOBALS['show_menu2_data'][$aMenu])) {
675
        $formatter = $aItemOpen;
676
        if (!is_object($aItemOpen)) {
677
            static $sm2formatter;
678
            if (!isset($sm2formatter)) {
679
                $sm2formatter = new SM2_Formatter;
680
            }
681
            $formatter = $sm2formatter;
682
            $formatter->set($flags, $aItemOpen, $aItemClose, 
683
                $aMenuOpen, $aMenuClose, $aTopItemOpen, $aTopMenuOpen);
684
        }
685
        
686
        // adjust the level until we show everything and ignore the SM2_TRIM flag.
687
        // Usually this will be less than the start level to disable it.
688
        $showAllLevel = $aStartLevel - 1;
689
        if (isset($aOptions['notrim'])) {
690
            $showAllLevel = $aStartLevel + $aOptions['notrim'];
691
        }
692
        
693
        // display the menu
694
        $formatter->initialize();
695
        sm2_recurse(
696
            $GLOBALS['show_menu2_data'][$aMenu],
697
            $aStart,    // parent id to start displaying sub-menus
698
            $aStartLevel, $showAllLevel, $aMaxLevel, $flags, 
699
            $formatter);
700
        $formatter->finalize();
701
        
702
        // if we are returning something, get the data
703
        if (($flags & SM2_BUFFER) != 0) {
704
            $retval = $formatter->getOutput();
705
        }
706
    }
707

    
708
    // clear the data if we aren't caching it
709
    if (($flags & SM2_NOCACHE) != 0) {
710
        unset($GLOBALS['show_menu2_data'][$aMenu]);
711
    }
712
    
713
    return $retval;
714
}
715

    
716
function sm2_mark_children(&$rgParent, $aStart, $aChildLevel)
717
{
718
    if (array_key_exists($aStart, $rgParent)) {
719
        foreach (array_keys($rgParent[$aStart]) as $y) {
720
            $mark =& $rgParent[$aStart][$y];
721
            $mark['sm2_child_level'] = $aChildLevel;
722
            $mark['sm2_on_curr_path'] = true;
723
            sm2_mark_children($rgParent, $mark['page_id'], $aChildLevel+1);
724
        }
725
    }
726
}
727

    
728
function sm2_recurse(
729
    &$rgParent, $aStart, 
730
    $aStartLevel, $aShowAllLevel, $aMaxLevel, $aFlags, 
731
    &$aFormatter
732
    )
733
{
734
    global $wb;
735

    
736
    // on entry to this function we know that there are entries for this 
737
    // parent and all entries for that parent are being displayed. We also 
738
    // need to check if any of the children need to be displayed too.
739
    $isListOpen = false;
740
    $currentLevel = $wb->page['level'] == '' ? 0 : $wb->page['level'];
741

    
742
    // get the number of siblings skipping the hidden pages so we can pass 
743
    // this in and check if the item is first or last
744
    $sibCount = 0;
745
    foreach ($rgParent[$aStart] as $page) {
746
        if (!array_key_exists('sm2_hide', $page)) $sibCount++;
747
    }
748
    
749
    $currSib = 0;
750
    foreach ($rgParent[$aStart] as $page) {
751
        // skip all hidden pages 
752
        if (array_key_exists('sm2_hide', $page)) { // not set if false, so existence = true
753
            continue;
754
        }
755
        
756
        $currSib++;
757

    
758
        // skip any elements that are lower than the maximum level
759
        $pageLevel = $page['level'];
760
        if ($pageLevel > $aMaxLevel) {
761
            continue;
762
        }
763
        
764
        // this affects ONLY the top level
765
        if ($aStart == 0 && ($aFlags & SM2_CURRTREE)) {
766
            if (!array_key_exists('sm2_on_curr_path', $page)) { // not set if false, so existence = true
767
                continue;
768
            }
769
            $sibCount = 1;
770
        }
771
        
772
        // trim the tree as appropriate
773
        if ($aFlags & SM2_SIBLING) {
774
            // parents, and siblings and children of current only
775
            if (!array_key_exists('sm2_on_curr_path', $page)      // not set if false, so existence = true
776
                && !array_key_exists('sm2_is_sibling', $page)     // not set if false, so existence = true
777
                && !array_key_exists('sm2_child_level', $page)) { // not set if false, so existence = true
778
                continue;
779
            }
780
        }
781
        else if ($aFlags & SM2_TRIM) {
782
            // parents and siblings of parents
783
            if ($pageLevel > $aShowAllLevel  // permit all levels to be shown
784
                && !array_key_exists('sm2_on_curr_path', $page)    // not set if false, so existence = true
785
                && !array_key_exists('sm2_path_sibling', $page)) {  // not set if false, so existence = true
786
                continue;
787
            }
788
        }
789
        elseif ($aFlags & SM2_CRUMB) {
790
            // parents only
791
            if (!array_key_exists('sm2_on_curr_path', $page)    // not set if false, so existence = true
792
                || array_key_exists('sm2_child_level', $page)) {  // not set if false, so existence = true
793
                continue;
794
            }
795
        }
796

    
797
        // depth first traverse
798
        $nextParent = $page['page_id'];
799

    
800
        // display the current element if we have reached the start level
801
        if ($pageLevel >= $aStartLevel) {
802
            // massage the link into the correct form
803
            if(!INTRO_PAGE && $page['link'] == $wb->default_link) {
804
                $url = WB_URL;
805
            }
806
            else {
807
                $url = $wb->page_link($page['link']);
808
            }
809
                    
810
            // we open the list only when we absolutely need to
811
            if (!$isListOpen) {
812
                $aFormatter->startList($page, $url);
813
                $isListOpen = true;
814
            }
815

    
816
            $aFormatter->startItem($page, $url, $currSib, $sibCount);
817
        }
818
        
819
        // display children as appropriate
820
        if ($pageLevel + 1 <= $aMaxLevel 
821
            && array_key_exists('sm2_has_child', $page)) {  // not set if false, so existence = true
822
            sm2_recurse(
823
                $rgParent, $nextParent, // parent id to start displaying sub-menus
824
                $aStartLevel, $aShowAllLevel, $aMaxLevel, $aFlags, 
825
                $aFormatter);
826
        }
827
        
828
        // close the current element if appropriate
829
        if ($pageLevel >= $aStartLevel) {
830
            $aFormatter->finishItem($pageLevel, $page);
831
        }
832
    }
833

    
834
    // close the list if we opened one
835
    if ($isListOpen) {
836
        $aFormatter->finishList();
837
    }
838
}
839

    
840
?>
(4-4/7)