diff --git a/Lib/misc/nav_functions.php b/Lib/misc/nav_functions.php new file mode 100644 index 000000000..033e7e2da --- /dev/null +++ b/Lib/misc/nav_functions.php @@ -0,0 +1,1032 @@ + style nav link with 'active' class added if is current page + * + * @param array $params assoc array: text,path,title,css,id,icon + * @return string
  • link + */ +function makeListLink($params) { + global $route; + $activeClassName = 'active'; + + $li_id = getKeyValue('li_id', $params); + $li_class = (array) getKeyValue('li_class', $params); + $li_style = (array) getKeyValue('li_style', $params); + + $id = getKeyValue('id', $params); + $text = getKeyValue('text', $params); + $path = getKeyValue('path', $params); + $href = getKeyValue('href', $params); + $title = getKeyValue('title', $params); + $icon = getKeyValue('icon', $params); + $active = getAbsoluteUrl(getKeyValue('active', $params)); + $sub_items = (array) getKeyValue('sub_items', $params); + $style = (array) getKeyValue('style', $params); + $class = (array) getKeyValue('class', $params); + $data = (array) getKeyValue('data', $params); + $data['active'] = $active; + + $data = array_filter($data);// clean out empty entries + + if(is_current($path) || is_current($active) || is_active($params)){ + $li_class[] = $activeClassName; + } + + if(empty($title)) $title = $text; + + $link = makeLink(array( + 'text'=> $text, + 'title'=> $title, + 'class'=> $class, + 'id'=> $id, + 'icon'=> $icon, + 'href'=> $href, + 'path'=> $path, + 'active'=> $active, + 'data'=> $data, + 'style'=> $style + )); + + $attr = buildAttributes(array( + 'id' => $li_id, + 'class' => implode(' ', array_unique($li_class)), + 'style' => implode(';', $li_style) + )); + if(!empty($attr)) $attr = ' '.$attr; + + $sub_items = array_filter($sub_items); + if(!empty($sub_items)) { + foreach($sub_items as $key=>$item) { + if(is_array($item)) { + $sub_items[$key] = makeListLink($item); + } + } + $link .= ''; + } + return empty($link) ? '' : sprintf('%s
  • ', $attr, $link); +} +/** + * returns true if current view's url matches passed $path(s) + * + * @param mixed $_url single or list of urls to check + * @return boolean + */ +function is_current($_url = array()) { + $current_url = getAbsoluteUrl($_SERVER['REQUEST_URI']); + foreach((array) $_url as $search) { + $search_url = getAbsoluteUrl($search); + if($search_url === $current_url) { + return true; + } + } + return false; +} +/** + * returns true if current view's path matches passed $path(s) + * + * @param mixed $path - array can be passed to match multiple paths + * @return boolean + */ +function path_is_current($path) { + $current_path = current_route(); + foreach((array) $path as $search) { + $search_path = parse_url(getAbsoluteUrl($search), PHP_URL_PATH); + if ($search_path === $current_path || count(explode('/',$search_path)) > 3 + && strpos($current_path, $search_path) === 0) + { + return true; + } + } + return false; +} +/** + * return the current route path + * + * @return void + */ +function current_route() { + return parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); +} +/** + * return $array[$key] value if not empty + * else return empty string + * + * @param string $key + * @param array $array + * @return mixed + */ +function getKeyValue($key, $array) { + return isset($array[$key]) ? $array[$key] : ''; +} +/** + * return string with new line and multiple tabs + * eg: tab(3) would return "\n\t\t\t" + * + * @param integer $num number of tabs to return + * @return string + */ +function tab($num){ + return "\n".str_pad('',$num,"\t"); +} +/** + * build link with 'active' class added if is current page + * $params = assoc array with keys: [text|path|title|class|id|icon|active|href|data] + * + * @param array $params associative array + * @return string tag + */ +function makeLink($params) { + $activeClassName = 'active'; + + $text = getKeyValue('text', $params); + $path = getKeyValue('path', $params); + $href = getKeyValue('href', $params); + $title = getKeyValue('title', $params); + $id = getKeyValue('id', $params); + $icon = getKeyValue('icon', $params); + $active = getKeyValue('active', $params); + + $style = (array) getKeyValue('style', $params); + $class = (array) getKeyValue('class', $params); + $data = (array) getKeyValue('data', $params); + // create url if pre-built url not passed + if(empty($href)) $href = getAbsoluteUrl($path); + + // append icon to link text + if (!empty($icon)) { + $icon = sprintf(' ', $icon); + $text = sprintf('%s%s', $icon, $text); + $class[] = 'd-flex flex-nowrap justify-items-between'; + } + // add active class to link if link is to current page + if (!empty($active)) { + if(is_current($active)){ + $class[] = $activeClassName; + } + } elseif(is_current($path)){ + $class[] = $activeClassName; + } + + // create the tag attribute list + $attr = buildAttributes(array( + 'id'=>$id, + 'href'=>$href, + 'style'=>implode(';', $style), + 'title'=>$title, + 'class'=>implode(' ', $class), + 'data'=>$data + )); + // exit function if no href value available + if(empty($href)) return $text; + + // return tag with all the attributes and child elements + return sprintf('%s', $attr, $text); +} +/** + * return html element attribute string (eg. key1="value1" key2="value2") + * if value is array multiples are grouped (eg. key="value1 value2") + * if value is array and key is "data" then array values are prefixed with data-[key]. (eg. data-name="value" data-size="value") + * + * example of use: + * --------------- + * simple pairs: buildAttributes(array('id'=>'menu-item-2')) ==> 'id="menu-item-2"' + * data-* style: buildAttributes(array('data'=>array('toggle'=>'collapse','target'=>'#sidebar'))) + * ==> 'data-toggle="collapse" data-target="#sidebar"' + * grouped style: buildAttributes(array('class'=>array('dark','large'))) ==> 'class="dark large"' + * + * @param array $attributes + * @return string + */ +function buildAttributes($attributes){ + return implode(' ', array_filter( array_map(function($key) use ($attributes) { + $value = $attributes[$key]; + // print_r($value); + if (!empty($value)) { + if(!is_array($value)) { + // return simple key=value pair + return $key.'="'.$value.'"'; + } else { + if(isSequential($value)){ + // join multi-value properties + // eg css="a b c" etc + $list[$key] = implode(' ', array_unique($value)); + } else { + foreach($value as $key2=>$value2) { + if($key==='data') $key2 = 'data-'.$key2; + // add the data-* attribute + // eg data-'close' data-'open' + $list[$key2] = $value2; + } + } + // call itself array values as strings + return buildAttributes($list); + } + } + }, array_keys($attributes)))); +} + +/** + * return full url of given relative path of controller/action + * + * @param string $_path + * @return string eg. http://localhost/emoncms/feed/list + */ +function getAbsoluteUrl($_passedPath) { + if(empty($_passedPath)) return ''; + global $path; + // if passed path ($_passedPath) begins with /emoncms remove it + $_passedPathParts = getPathParts($_passedPath); + // if first path part is 'emoncms' remove it + if(getKeyValue(0, $_passedPathParts)=='emoncms') { + array_shift($_passedPathParts); + } + // add to global $path + $url = $path . implode('/', $_passedPathParts); + + // url query parts + $query_parts = getQueryParts($_passedPath); + // return emoncms $path with the relative $_passedPath component; + foreach($query_parts as $key=>$value) { + $q[] = sprintf("%s=%s", $key, $value); + } + // add query parts of url. eg. ?q=bluetooth+mouse&sort=asc + if(!empty($q)) { + $query = implode('&', $q); + $url .= '?' . $query; + } + // encode the url parts like a application/x-www-form-urlencoded + return encodePath($url); +} + +// /** +// * add a css class name to a given list (if not already there) +// * +// * @param string $classname +// * @param mixed $css array | string +// * @return string +// */ +// function addCssClass($classname, $css) { +// if(!is_array($css)) $css = explode(' ', $css); +// $css = array_unique(array_filter($css)); +// if (!in_array($classname, $css)){ +// $css[] = $classname; +// } +// $css = implode(' ', $css); +// return $css; +// } + +/** + * for development only + * + * @param string $key - print sub array if $key supplied + * @return void - exits php after printing array + */ +function debugMenu($key = '') { + global $menu; + echo "
    ";
    +    if(!empty($key) && isset($menu[$key]) && $key !== 'includes'){
    +        printf("%s:\n-------------\n",strtoupper($key));
    +        print_r($menu[$key]);
    +    } else {
    +        print_r($menu);
    +    }
    +    exit('eof debugMenu()');
    +}
    +/**
    + * sort all the menus individually. items without sort are added to bottom a-z
    + * calls itself again until $array is a menu item
    + *
    + * @param array $array
    + * @return void
    + */
    +function sortMenu (&$menus) {
    +    foreach($menus as $name=>&$menu) {
    +        // includes don't follow same structure
    +        if ($name === 'includes') return;
    +        
    +        // if $menu has numeric keys it has menu items
    +        // eg $menu[0]
    +        if (isSequential($menu)) {
    +            // collect indexes for unordered items
    +            $orders = array();
    +            $unordered = array();
    +            if(is_array($menu)) {
    +                foreach($menu as $key=>&$item) {
    +                    encodeMenuItemUrl($item);
    +                    $item['path'] = getAbsoluteUrl($item['path']);
    +
    +                    if (isset($item['order'])) {
    +                        $orders[] = $item['order'];
    +                    } else {
    +                        $unordered[] = $key;
    +                    }
    +                }
    +            }
    +            // get next sort (max_order) for a menu
    +            $next_order = !empty($orders) ? max($orders)+1: 0;
    +            
    +            // set order field for un-ordered menu items
    +            foreach($unordered as $index) {
    +                // sort by title if available
    +                if(isset($menu[$index]['text'])) {
    +                    $menu[$index]['order'] = $menu[$index]['text'];
    +                } else {
    +                // sort by integer if not title available
    +                    $menu[$index]['order'] = $next_order++;
    +                }
    +            }
    +            // re-index menu based on 'order' value
    +            if(is_array($menu)) usort($menu, 'sortMenuItems');
    +
    +        // if $menu has alpha-numeric keys it is a menu group
    +        // eg. $menu['setup']
    +        } else {
    +            // call sort again for sub-menus
    +            sortMenu($menu);
    +        }
    +    }
    +}
    +
    +/**
    + * url encoding a give menu item's path
    + *
    + * @param array $item
    + * @return void;
    + */
    +function encodeMenuItemUrl(&$item) {
    +    if(isset($item['path'])) {
    +        $item['path'] = encodePath($item['path']);
    +    } else {
    +        $item['path'] = '';
    +    }
    +}
    +/**
    + * return url encoded string
    + * 
    + * individually encode the parts of given $path string
    + *
    + * @param string $path
    + * @return string
    + */
    +function encodePath($path){
    +    // split url into parts
    +    $parts = parse_url($path);
    +    // encode url path parts
    +    if(isset($parts['path'])) {
    +        $path_parts = [];
    +        foreach(getPathParts($path) as $p) {
    +            $path_parts[] = urlencode($p);
    +        }
    +        $parts['path'] = implode('/', $path_parts);
    +    }
    +    
    +    if(isset($parts['query'])) {
    +        $query_parts = getQueryParts($path);
    +        $query_parts = array_map('urldecode', $query_parts);
    +        $parts['query'] = http_build_query($query_parts);
    +    }
    +
    +    $url = (isset($parts['scheme']) ? "{$parts['scheme']}:" : '') . 
    +    ((isset($parts['user']) || isset($parts['host'])) ? '//' : '') . 
    +    (isset($parts['host']) ? "{$parts['host']}" : '') . 
    +    (isset($parts['port']) ? ":{$parts['port']}" : '') . 
    +    (isset($parts['path']) ? "/{$parts['path']}" : '') . 
    +    (isset($parts['query']) ? "?{$parts['query']}" : '');
    +
    +    return $url;
    +}
    +
    +/**
    + * return true if all array keys are not a string (aka 'non-associative' array)
    + *
    + * @param array $array
    + * @return boolean
    + */ 
    +function isSequential($array = array()) {
    +    $array = (array) $array;
    +    $string_keys = array_filter(array_keys($array), 'is_string');
    +    return count($string_keys) == 0;
    +}
    +
    +/**
    + * used as usort() sorting function
    + * 
    + * return -1 if $a['order'] is less than $b['order']
    + * return 0 if $a['order'] is equal to $b['order']
    + * return 1 if $a['order'] is greater than $b['order']
    + *
    + * @param array $a
    + * @param array $b
    + * @return int
    + */
    +function sortMenuItems ($a, $b) {
    +    $orderby = 'order';
    +    $ac = getKeyValue($orderby, $a);
    +    $bc = getKeyValue($orderby, $b);
    +    return strcmp($ac, $bc);
    +}
    +
    +/**
    + * return true if menu item path is the current page or $passed_path
    + *
    + * @param array $item - menu item with ['path'] property
    + *                      if not array passed attempt to locate the menu item by passed string
    + *                      defaults to current route if empty
    + * @param string $passed_path - check menu item against this url
    + * @return boolean
    + */
    +function is_active($item = null, $passed_path = null) {
    +    global $route, $path;
    +    $slash = '/';
    +    $base = !empty($passed_path) ? $passed_path: $path;
    +    // if passed item is not an array look it up by path
    +    if (!is_array($item)) $item = getSidebarItem($item);
    +    if (!$item) $item = getCurrentMenuItem();
    +    // remove the full $path from the link's absolute url
    +    $_path = str_replace($base, '', getKeyValue('path', $item));
    +    $_active = str_replace($base, '', getKeyValue('active', $item));
    +    $q = !empty($route->query) ? "?".$route->query: '';
    +    // check for different combos of controllers and actions for a match
    +    if ($_path === implode($slash, array_filter(array($route->controller, $route->action, $route->subaction, $route->subaction2))) ||
    +        $_path === implode($slash, array_filter(array($route->controller, $route->action))).$q ||
    +        $_active === implode($slash, array_filter(array($route->controller))) ) {
    +        return true;
    +    }
    +    return false;
    +}
    +/**
    + * return true if passed $item has desired properties for bing a menu item
    + *
    + * @param array $item
    + * @return boolean
    + */
    +function is_menu_item($item) {
    +    if(is_array($item) && (
    +        !empty($item['id'])   ||
    +        !empty($item['path']) ||
    +        !empty($item['href']) ||
    +        !empty($item['icon']) ||
    +        !empty($item['text']) ||
    +        !empty($item['title'])
    +    )) {
    +        return true;
    +    }
    +    return false;
    +}
    +/**
    + * call the makeListLink() function after modifiying the menu item to act as a dropdown
    + *
    + * @param array $item - $menu array item
    + * @return void
    + */
    +function makeDropdown($item){
    +    global $session;
    +    // add empty text value to avoid title from being used
    +    if(empty($item['text'])) $item['text'] = '';
    +    
    +    // add empty title value to avoid text with icon from being used
    +    if(empty($item['title'])) $item['title'] = $item['text'];
    +    
    +    // add the dropdown indicator
    +    // $item['text'] .= ' ';
    +
    +    // add the correct class to the 
  • + $item['li_class'][] = 'dropdown'; + + if(is_current_menu($item['sub_items'])){ + $item['li_class'][] = 'active'; + } + + // create variable if empty + if(!isset($item['class'])) $item['class'] = ''; + + // add additional css classes to
  • + settype($item['class'], 'array'); + $item['class'][] = 'dropdown-toggle'; + + // add data-* attributes + $item['data']['toggle'] = 'dropdown'; + + // return
  • with sub