Seditio Source
Root |
./othercms/b2evolution_7.2.3/inc/_core/ui/_menu.class.php
<?php
/**
 * This file implements the Menu class.
 *
 * This file is part of the evoCore framework - {@link http://evocore.net/}
 * See also {@link https://github.com/b2evolution/b2evolution}.
 *
 * @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license}
 *
 * @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}
 * Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}.
 *
 * @package evocore
 */
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );

load_class( '_core/ui/_uiwidget.class.php', 'Widget' );

/**
 * Menu class
 *
 * @package evocore
 */
class Menu extends Widget
{
   
/**
     * The menu structure (array of arrays)
     *
     * Use {@link add_menu_entries()} to add them here.
     *
     * @access protected
     * @var array
     */
   
var $_menus = array();


   
/**
     * Add menu entries to the list of entries for a given path.
     *
     * @param NULL|string|array The path to add the entries to. See {@link get_node_by_path()}.
     * @param array Menu entries to add (key (string) => entry (array)).
     *   An entry can have the following keys:
     *     'text': Text/Caption for this entry.
     *     'href': The link for this entry.
     *     'entries': array of sub-entries
     *     DEPRECATED 'style': CSS style for this entry.
     *     DEPRECATED 'onclick': onclick property for this entry.
     *     DEPRECATED 'name': name attribute of the link/entry.
     * @param string Node name after which we should insert the menu entries, NULL - to insert to the end
     */
   
function add_menu_entries( $path, $new_entries, $after_node = NULL )
    {
       
// Get a reference to the node in the menu list.
       
$node = & $this->get_node_by_path( $path, true );

       
/*
        if( !is_array($node) )
        {
            debug_die( 'add_menu_entries() with non-existing path!' );
        }
        */

       
$new_entires_are_inserted = false;

        if( !
is_null( $after_node ) )
        {    
// We should insert new entries after specific node
           
$new_node = $node;
           
$new_node['entries'] = array();
            if( isset(
$node['entries'] ) )
            {
                foreach(
$node['entries'] as $node_key => $node_entry )
                {
                   
$new_node['entries'][ $node_key ] = $node_entry;
                    if(
$node_key == $after_node )
                    {    
// Insert new entires here after specific node
                       
foreach( $new_entries as $l_key => $l_new_entry )
                        {
                           
$new_node['entries'][$l_key] = $l_new_entry;
                        }
                       
$new_entires_are_inserted = true;
                    }
                }
            }
           
$node = $new_node;
        }

        if( !
$new_entires_are_inserted )
        {    
// Insert new entries to the end if they are still not inserted after specific node
           
foreach( $new_entries as $l_key => $l_new_entry )
            {
               
$node['entries'][$l_key] = $l_new_entry;
            }
        }
    }

   
/**
     * Insert new menu entries right after the menu entry passed as path
     *
     * @param NULL|string|array The path. See {@link get_node_by_path()}.
     * @param new entries
     * @param position after which to insert new entries. If not given, new entries is inserted after Path
     * @returns boolean Whether inserting was successfull.
     */
   
function insert_menu_entries_after( $path, $new_entries, $index = false )
    {
       
$menu_item = '';
        if(
$index === false )
        {
// get menu item after which to insert new entries

           
if( is_array( $path ) )
            {
               
$menu_item = array_pop( $path );
            }
            elseif(
is_string( $path ) )
            {
               
$menu_item = $path;
               
$path = NULL;
            }
        }
       
$menu = & $this->get_node_by_path( $path );
        if(
$menu === false )
        {
// no such path
           
return false;
        }
       
$entries = & $menu['entries'];
        if(
$menu_item )
        {
// find index of menu itemafter which to insert new entries
           
$keys = array_keys( $entries );

            if( !empty(
$keys ) ) $index = array_search( $menu_item, $keys, true );
        }

        if( (
$index === false ) || ($index === NULL) )
        {
            return
false;
        }

       
// make new menu entries
       
$menu['entries'] = array_merge(
           
array_slice( $entries, 0, $index + 1 ),
           
$new_entries,
           
array_slice( $entries, $index )
        );

        return
true;
    }

   
/**
     * Get the reference of a node from the menu entries using a path.
     *
     * @param array|string|NULL The path. NULL means root, string means child of root,
     *                          array means path below root. (eg <code>array('options', 'general')</code>).
     * @param boolean Should the node be created if it does not exist already?
     * @return array|false The node as array or false, if the path does not exist (and we do not $createIfNotExisting).
     */
   
function & get_node_by_path( $path, $createIfNotExisting = false )
    {
        if(
is_null($path) )
        {
// root element
           
$path = array();
        }
        elseif( !
is_array($path) )
        {
           
$path = array($path);
        }

       
$node = & $this->_menus;
        foreach(
$path as $lStep )
        {
            if( ! isset(
$node['entries'][$lStep]) )
            {
                if(
$createIfNotExisting )
                {
                   
$node['entries'][$lStep] = array();
                }
                else
                {
                   
$r = false;
                    return
$r;
                }
            }
           
$node = & $node['entries'][$lStep];
        }

        return
$node;
    }


   
/**
     * Get menu entries for a given path.
     *
     * @param NULL|string|array The path. See {@link get_node_by_path()}.
     * @return array The menu entries (may be empty).
     */
   
function get_menu_entries( $path )
    {
       
$node = & $this->get_node_by_path( $path );

        return isset(
$node['entries'] ) ? $node['entries'] : array();
    }


   
/**
     * Get the key of a selected entry for a path.
     *
     * @param NULL|string|array The path. See {@link get_node_by_path()}.
     * @return string|false
     */
   
function get_selected( $path )
    {
       
$node = & $this->get_node_by_path($path);

        if( isset(
$node['selected']) )
        {
            return
$node['selected'];
        }

        return
false;
    }


   
/**
     * Get the HTML for the menu entries of a specific path.
     *
     * @param NULL|string|array The path. See {@link get_node_by_path()}.
     * @param string Template name, see {@link get_template()}.
     * @param integer Level
     * @param boolean TRUE - to get template for empty menu, used to hide menus
     * @return string The HTML for the menu.
     */
   
function get_html_menu( $path = NULL, $template = 'main', $level = 0, $force_empty = false )
    {
       
$r = '';

        if(
is_null($path) )
        {
           
$path = array();
        }
        elseif( !
is_array( $path ) )
        {
           
$path = array( $path );
        }

       
$templateForLevel = $this->get_template( $template, $level );

        if(
$force_empty || !( $menuEntries = $this->get_menu_entries($path) ) )
        {    
// No menu entries at this level
           
if( isset($templateForLevel['empty']) )
            {
               
$r .= $templateForLevel['empty'];
            }
        }
        else
        {    
// There are entries to display:
           
$r .= $templateForLevel['before'];

           
$selected = $this->get_selected($path);

            foreach(
$menuEntries as $loop_key => $loop_details )
            {
                if( empty(
$loop_details ) )
                {
// Empty placeholder, skip it. Might happen if the files module is disabled for example, then we had a file placeholder
                    // in the blog menu that will never be used. So don't display it...
                   
continue;
                }

                if( !empty(
$loop_details['separator'] ) )
                {
// Separator
                   
$r .= $templateForLevel['separator'];
                    continue;
                }

                if( ! empty(
$loop_details['entries'] ) )
                {
// Has sub-entries, check if any sub-entry can be displayed
                   
$has_entry = false;
                    foreach(
$loop_details['entries'] as $entry_key => $entry_details )
                    {
                        if( !empty(
$entry_details ) )
                        {
// Menu has non-empty entry
                           
$has_entry = true;
                            break;
                        }
                    }
                    if( !
$has_entry )
                    {
                        continue;
                    }
                }

               
// Menu entry
               
if( isset( $loop_details['href'] ) )
                {
                   
$href = $loop_details['href'];
                }
                elseif( ! empty(
$loop_details['href_eval'] ) )
                {
// Useful for passing dynamic context vars (fp>> I AM using it)
                   
$href = eval( $loop_details['href_eval'] );
                }
                else
                {
                   
$href = NULL;
                }

               
$anchor = '<a';

                if( !empty(
$href) )
                {
                   
$anchor .= ' href="'.$href.'"';
                }
                if( isset(
$loop_details['target']) )
                {
                   
$anchor .= ' target="'.$loop_details['target'].'"';
                }
                if( isset(
$loop_details['rel']) )
                {
                   
$anchor .= ' rel="'.$loop_details['rel'].'"';
                }
                if( isset(
$loop_details['style']) )
                {
                   
$anchor .= ' style="'.$loop_details['style'].'"';
                }
                if( isset(
$loop_details['onclick']) )
                {
                   
$anchor .= ' onclick="'.$loop_details['onclick'].'"';
                }
                if( isset(
$loop_details['name']) )
                {
                   
$anchor .= ' name="'.$loop_details['name'].'"';
                }
                if( isset(
$loop_details['title']) )
                {
                   
$anchor .= ' title="'.$loop_details['title'].'"';
                }
                if( isset(
$loop_details['shortcut']) )
                {
                   
$anchor .= ' data-shortcut="'.$loop_details['shortcut'].'"';
                }
                if( isset(
$loop_details['shortcut-top']) )
                {
                   
$anchor .= ' data-shortcut-top="'.$loop_details['shortcut-top'].'"';
                }

               
// CLASS
               
$class = '';
                if( !empty(
$loop_details['class'] ) )
                {
// disabled
                   
$class .= ' '.$loop_details['class'];
                }
                if( !empty(
$loop_details['disabled'] ) )
                {
// disabled
                   
$class .= ' '.$templateForLevel['disabled_class'];
                }
                if( ! empty(
$class ) )
                {
// disabled
                   
$anchor .= ' class="'.trim($class).'"';
                }

               
$anchor .= '>'.(isset($loop_details['text']) ? format_to_output( $loop_details['text'], 'htmlbody' ) : '?');
               
$anchor_end = '</a>';

                if(
$loop_key == $selected )
                {
// Highlight selected entry
                   
if( isset( $templateForLevel['_props']['recurse'] )
                            &&
$templateForLevel['_props']['recurse'] != 'no'
                           
&& ( $recursePath = array_merge( $path, array($loop_key) ) )
                            &&
$this->get_menu_entries($recursePath) )
                    {
                       
$r .= isset($templateForLevel['beforeEachSelWithSub']) ? $templateForLevel['beforeEachSelWithSub'] : $templateForLevel['beforeEachSel'];
                       
$r .= $anchor;

                        if(
$templateForLevel['_props']['recurse'] != 'no' && // Recurse:
                             
( ! isset( $templateForLevel['_props']['recurse_level'] ) ||
                                ( isset(
$templateForLevel['_props']['recurse_level'] ) &&
                                 
$templateForLevel['_props']['recurse_level'] > $level + 1 ) ) )
                        {
// Display submenus if this level is not limited by param 'recurse_level'
                           
$r .= $templateForLevel['arrow_level_'.( $level + 1 )].'</a>';
                           
$r .= $this->get_html_menu( $recursePath, $template, $level+1 );
                        }
                        else
                        {
// End anchor without sub menus
                           
$r .= $anchor_end;
                        }

                       
$r .= isset($templateForLevel['afterEachSelWithSub']) ? $templateForLevel['afterEachSelWithSub'] : $templateForLevel['afterEachSel'];
                    }
                    else
                    {
                        if( isset(
$loop_details['order'] ) && $loop_details['order'] == 'group_last' &&
                            isset(
$templateForLevel['beforeEachSelGrpLast'], $templateForLevel['afterEachSelGrpLast'] ) )
                        {
// This selected menu item is last in a group
                           
$r .= $templateForLevel['beforeEachSelGrpLast'];
                           
$r .= $anchor.$anchor_end;
                           
$r .= $templateForLevel['afterEachSelGrpLast'];
                        }
                        else
                        {
// Normal selected menu item
                           
$r .= $templateForLevel['beforeEachSel'];
                           
$r .= $anchor.$anchor_end;
                           
$r .= $templateForLevel['afterEachSel'];
                        }
                    }
                }
                else
                {    
// Not selected entry
                   
if( isset( $templateForLevel['_props']['recurse'] )
                            &&
$templateForLevel['_props']['recurse'] == 'always'
                           
&& ( $recursePath = array_merge( $path, array($loop_key) ) )
                            &&
$this->get_menu_entries($recursePath) )
                    {
                       
$r .= isset($templateForLevel['beforeEachWithSub']) ? $templateForLevel['beforeEachWithSub'] : $templateForLevel['beforeEachSel'];
                       
$r .= $anchor.$templateForLevel['arrow_level_'.( $level + 1 )].'</a>';
                       
// recurse:
                       
$r .= $this->get_html_menu( $recursePath, $template, $level+1 );
                       
$r .= isset($templateForLevel['afterEachWithSub']) ? $templateForLevel['afterEachWithSub'] : $templateForLevel['afterEachSel'];
                    }
                    else
                    {
                        if( isset(
$loop_details['order'] ) && $loop_details['order'] == 'group_last' &&
                            isset(
$templateForLevel['beforeEachGrpLast'], $templateForLevel['afterEachGrpLast'] ) )
                        {
// This menu item is last in a group
                           
$r .= $templateForLevel['beforeEachGrpLast'];
                           
$r .= $anchor.$anchor_end;
                           
$r .= $templateForLevel['afterEachGrpLast'];
                        }
                        else
                        {
// Normal menu item
                           
$r .= $templateForLevel['beforeEach'];
                           
$r .= $anchor.$anchor_end;
                           
$r .= $templateForLevel['afterEach'];
                        }
                    }
                }

               
// Additional attribures for each menu entry
               
$entry_attrs = '';
               
$entry_class = '';
                if( ! empty(
$loop_details['entry_class'] ) )
                {
// Css class for entry
                   
if( strpos( $r, '$entry_class$' ) === false )
                    {
                       
$entry_attrs .= ' class="'.$loop_details['entry_class'].'"';
                    }
                   
$entry_class .= ' '.$loop_details['entry_class'];
                }
               
$r = str_replace( array( '$entry_attrs$', '$entry_class$' ), array( $entry_attrs, $entry_class ), $r );
            }
           
$r .= $templateForLevel['after'];
        }

        return
$r;
    }



   
/**
     * Get a template by name.
     *
     * This is a method (and not a member array) to allow dynamic generation and T_()
     *
     * @param string Name of the template ('main', 'sub')
     * @return array Associative array which defines layout and optionally properties.
     */
   
function get_template( $name, $level = 0 )
    {
        switch(
$name )
        {
            case
'evobar-menu-right':
               
$arrow_level_2 = '<span class="evobar-icon-left fa fa-caret-left"></span>';
            case
'evobar-menu-left':
                return array(
                   
'before'         => '<ul class="evobar-menu '.$name.'">',
                   
'after'          => '</ul>',
                   
'beforeEach'     => '<li$entry_attrs$>',
                   
'afterEach'      => '</li>',
                   
'beforeEachSel'  => '<li class="current$entry_class$"$entry_attrs$>',
                   
'afterEachSel'   => '</li>',
                   
'separator'      => '<li class="separator"><hr /></li>',
                   
'arrow_level_1'  => '<span class="evobar-icon-down fa fa-caret-down"></span>',
                   
'arrow_level_2'  => isset( $arrow_level_2 ) ? $arrow_level_2 : '<span class="evobar-icon-right fa fa-caret-right"></span>',
                   
'disabled_class' => 'disabled',
                   
'_props' => array(
                       
'recurse' => 'always',  // options are: 'no' 'always' or 'intoselected'
                   
),
                );
                break;

            default:
               
debug_die( 'Unknown $name for Menu::get_template(): '.var_export($name, true) );
        }
    }


   
/**
     * Check if menu is empty or contains at least one entry
     *
     * @return boolean true if the menu is not empty | false otherwise
     */
   
function has_entires()
    {
        return !empty(
$this->_menus );
    }


   
/**
     * Clear menu entries of the given path.
     *
     * @param NULL|string The path to clear the entries from. See {@link get_node_by_path()}.
     */
   
function clear_menu_entries( $path )
    {
       
// Get a reference to the node in the menu list.
       
$node = & $this->get_node_by_path( $path, true );

       
$node['entries'] = array();
    }
}

?>