Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
93.55% covered (success)
93.55%
29 / 31
CRAP
90.76% covered (success)
90.76%
167 / 184
Nav
0.00% covered (danger)
0.00%
0 / 1
93.55% covered (success)
93.55%
29 / 31
138.52
90.76% covered (success)
90.76%
167 / 184
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 returnFalse
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 setTree
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
2 / 2
 addBranch
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
4 / 4
 addLeaf
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 setConfig
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
9 / 9
 setAcl
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 setRole
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 addRole
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 addRoles
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 setAclStrict
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 setIndent
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 setBaseUrl
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 isReturnFalse
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 isAclStrict
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getTree
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getConfig
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getAcl
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 hasRoles
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 hasRole
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getRoles
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getRole
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
1 / 1
 getIndent
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getBaseUrl
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 build
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 rebuild
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 nav
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 render
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
4 / 4
 __toString
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 traverseTree
0.00% covered (danger)
0.00%
0 / 1
8.05
90.91% covered (success)
90.91%
10 / 11
 traverse
0.00% covered (danger)
0.00%
0 / 1
95.75
85.32% covered (success)
85.32%
93 / 109
<?php
/**
 * Pop PHP Framework (http://www.popphp.org/)
 *
 * @link       https://github.com/popphp/popphp-framework
 * @author     Nick Sagona, III <dev@nolainteractive.com>
 * @copyright  Copyright (c) 2009-2017 NOLA Interactive, LLC. (http://www.nolainteractive.com)
 * @license    http://www.popphp.org/license     New BSD License
 */
/**
 * @namespace
 */
namespace Pop\Nav;
use Pop\Acl\Acl;
use Pop\Acl\AclRole;
use Pop\Dom\Child;
/**
 * Nav class
 *
 * @category   Pop
 * @package    Pop\Nav
 * @author     Nick Sagona, III <dev@nolainteractive.com>
 * @copyright  Copyright (c) 2009-2017 NOLA Interactive, LLC. (http://www.nolainteractive.com)
 * @license    http://www.popphp.org/license     New BSD License
 * @version    3.0.0
 */
class Nav
{
    /**
     * Nav tree
     * @var array
     */
    protected $tree = [];
    /**
     * Nav config
     * @var array
     */
    protected $config = [];
    /**
     * Acl object
     * @var Acl
     */
    protected $acl = null;
    /**
     * AclRole role objects
     * @var array
     */
    protected $roles = [];
    /**
     * Acl strict flag
     * @var boolean
     */
    protected $aclStrict = false;
    /**
     * Indentation
     * @var string
     */
    protected $indent = null;
    /**
     * Base URL
     * @var string
     */
    protected $baseUrl = null;
    /**
     * Nav parent level
     * @var int
     */
    protected $parentLevel = 1;
    /**
     * Nav child level
     * @var int
     */
    protected $childLevel = 1;
    /**
     * Return false flag
     * @var boolean
     */
    protected $returnFalse = false;
    /**
     * Parent nav element
     * @var \Pop\Dom\Child
     */
    protected $nav = null;
    /**
     * Constructor
     *
     * Instantiate the nav object
     *
     * @param  array $tree
     * @param  array $config
     * @return self
     */
    public function __construct(array $tree = null, array $config = null)
    {
        $this->setTree($tree);
        $this->setConfig($config);
    }
    /**
     * Set the return false flag
     *
     * @param  boolean $return
     * @return Nav
     */
    public function returnFalse($return)
    {
        $this->returnFalse = (bool)$return;
        return $this;
    }
    /**
     * Set the nav tree
     *
     * @param  array $tree
     * @return Nav
     */
    public function setTree(array $tree = null)
    {
        $this->tree = (null !== $tree) ? $tree : [];
        return $this;
    }
    /**
     * Add to a nav tree branch
     *
     * @param  array   $branch
     * @param  boolean $prepend
     * @return Nav
     */
    public function addBranch(array $branch, $prepend = false)
    {
        if (isset($branch['name'])) {
            $branch = [$branch];
        }
        $this->tree = ($prepend) ? array_merge($branch, $this->tree) : array_merge($this->tree, $branch);
        return $this;
    }
    /**
     * Add to a leaf to nav tree branch
     *
     * @param  string  $branch
     * @param  array   $leaf
     * @param  int     $pos
     * @param  boolean $prepend
     * @return Nav
     */
    public function addLeaf($branch, array $leaf, $pos = null, $prepend = false)
    {
        $this->tree = $this->traverseTree($this->tree, $branch, $leaf, $pos, $prepend);
        $this->parentLevel = 1;
        $this->childLevel  = 1;
        return $this;
    }
    /**
     * Set the nav tree
     *
     * @param  array $config
     * @return Nav
     */
    public function setConfig(array $config = null)
    {
        if (null === $config) {
            $this->config = [
                'top'    => [
                    'node'  => 'nav'
                ],
                'parent' => [
                    'node'  => 'nav'
                ],
                'child' => [
                    'node'  => 'nav'
                ]
            ];
        } else {
            $this->config = $config;
        }
        if (isset($config['indent'])) {
            $this->setIndent($config['indent']);
        }
        if (isset($config['baseUrl'])) {
            $this->setBaseUrl($config['baseUrl']);
        }
        return $this;
    }
    /**
     * Set the Acl object
     *
     * @param  Acl $acl
     * @return Nav
     */
    public function setAcl(Acl $acl = null)
    {
        $this->acl = $acl;
        return $this;
    }
    /**
     * Set a AclRole object (alias method)
     *
     * @param  AclRole $role
     * @return Nav
     */
    public function setRole(AclRole $role = null)
    {
        $this->roles[$role->getName()] = $role;
        return $this;
    }
    /**
     * Add a AclRole object
     *
     * @param  AclRole $role
     * @return Nav
     */
    public function addRole(AclRole $role = null)
    {
        return $this->setRole($role);
    }
    /**
     * Add AclRole objects
     *
     * @param  array $roles
     * @return Nav
     */
    public function addRoles(array $roles)
    {
        foreach ($roles as $role) {
            $this->setRole($role);
        }
        return $this;
    }
    /**
     * Set the Acl object as strict evaluation
     *
     * @param  boolean $strict
     * @return Nav
     */
    public function setAclStrict($strict)
    {
        $this->aclStrict = (bool)$strict;
        return $this;
    }
    /**
     * Set the indent
     *
     * @param  string $indent
     * @return Nav
     */
    public function setIndent($indent)
    {
        $this->indent = $indent;
        return $this;
    }
    /**
     * Set the base URL
     *
     * @param  string $baseUrl
     * @return Nav
     */
    public function setBaseUrl($baseUrl)
    {
        $this->baseUrl = $baseUrl;
        return $this;
    }
    /**
     * Set the return false flag
     *
     * @return boolean
     */
    public function isReturnFalse()
    {
        return $this->returnFalse;
    }
    /**
     * Determine if the Acl object is set as strict evaluation
     *
     * @return boolean
     */
    public function isAclStrict()
    {
        return $this->aclStrict;
    }
    /**
     * Get the nav tree
     *
     * @return array
     */
    public function getTree()
    {
        return $this->tree;
    }
    /**
     * Get the config
     *
     * @return array
     */
    public function getConfig()
    {
        return $this->config;
    }
    /**
     * Get the Acl object
     *
     * @return Acl
     */
    public function getAcl()
    {
        return $this->acl;
    }
    /**
     * Determine if there are roles
     *
     * @return boolean
     */
    public function hasRoles()
    {
        return (count($this->roles) > 0);
    }
    /**
     * Determine if there is a certain role
     *
     * @param  string $name
     * @return boolean
     */
    public function hasRole($name)
    {
        return (isset($this->roles[$name]));
    }
    /**
     * Get the AclRole objects
     *
     * @return array
     */
    public function getRoles()
    {
        return $this->roles;
    }
    /**
     * Get a AclRole object
     *
     * @param  string $name
     * @return AclRole
     */
    public function getRole($name)
    {
        return (isset($this->roles[$name])) ? $this->roles[$name] : null;
    }
    /**
     * Get the indent
     *
     * @return string
     */
    public function getIndent()
    {
        return $this->indent;
    }
    /**
     * Get the base URL
     *
     * @return string
     */
    public function getBaseUrl()
    {
        return $this->baseUrl;
    }
    /**
     * Build the nav object
     *
     * @return Nav
     */
    public function build()
    {
        if (null === $this->nav) {
            $this->nav = $this->traverse($this->tree);
        }
        return $this;
    }
    /**
     * Re-build the nav object
     *
     * @return Nav
     */
    public function rebuild()
    {
        $this->parentLevel = 1;
        $this->childLevel  = 1;
        $this->nav = $this->traverse($this->tree);
        return $this;
    }
    /**
     * Get the nav object
     *
     * @return \Pop\Dom\Child
     */
    public function nav()
    {
        if (null === $this->nav) {
            $this->nav = $this->traverse($this->tree);
        }
        return $this->nav;
    }
    /**
     * Render the nav object
     *
     * @return string
     */
    public function render()
    {
        if (null === $this->nav) {
            $this->nav = $this->traverse($this->tree);
        }
        $result = ($this->nav->hasChildren()) ? $this->nav->render() : '';
        return $result;
    }
    /**
     * Render Nav object to string
     *
     * @return string
     */
    public function __toString()
    {
        return $this->render();
    }
    /**
     * Traverse tree to insert new leaf
     *
     * @param  array   $tree
     * @param  string  $branch
     * @param  array   $newLeaf
     * @param  int     $pos
     * @param  boolean $prepend
     * @param  int     $depth
     * @return array
     */
    protected function traverseTree($tree, $branch, $newLeaf, $pos = null, $prepend = false, $depth = 0)
    {
        $t = [];
        foreach ($tree as $leaf) {
            if (((null === $pos) || ($pos == $depth)) && ($leaf['name'] == $branch)) {
                if (isset($leaf['children'])) {
                    $leaf['children'] = ($prepend) ?
                        array_merge([$newLeaf], $leaf['children']) : array_merge($leaf['children'], [$newLeaf]);
                } else {
                    $leaf['children'] = [$newLeaf];
                }
            }
            if (isset($leaf['children'])) {
                $leaf['children'] = $this->traverseTree($leaf['children'], $branch, $newLeaf, $pos, $prepend, ($depth + 1));
            }
            $t[] = $leaf;
        }
        return $t;
    }
    /**
     * Traverse the config object
     *
     * @param  array  $tree
     * @param  int    $depth
     * @param  string $parentHref
     * @throws Exception
     * @return \Pop\Dom\Child
     */
    protected function traverse(array $tree, $depth = 1, $parentHref = null)
    {
        // Create overriding top level parent, if set
        if (($depth == 1) && isset($this->config['top'])) {
            $parent = (isset($this->config['top']) && isset($this->config['top']['node'])) ? $this->config['top']['node'] : 'nav';
            $child  = null;
            if (isset($this->config['child']) && isset($this->config['child']['node'])) {
                $child = $this->config['child']['node'];
            } else if ($parent == 'nav') {
                $child = 'nav';
            }
            // Create parent node
            $nav = new Child($parent);
            if (null !== $this->indent) {
                $nav->setIndent(str_repeat($this->indent, $depth));
            }
            // Set top attributes if they exist
            if (isset($this->config['top']) && isset($this->config['top']['id'])) {
                $nav->setAttribute('id', $this->config['top']['id']);
            }
            if (isset($this->config['top']) && isset($this->config['top']['class'])) {
                $nav->setAttribute('class', $this->config['top']['class']);
            }
            if (isset($this->config['top']['attributes'])) {
                foreach ($this->config['top']['attributes'] as $attrib => $value) {
                    $nav->setAttribute($attrib, $value);
                }
            }
        } else {
            // Set up parent/child node names
            $parent = (isset($this->config['parent']) && isset($this->config['parent']['node'])) ? $this->config['parent']['node'] : 'nav';
            $child  = null;
            if (isset($this->config['child']) && isset($this->config['child']['node'])) {
                $child = $this->config['child']['node'];
            } else if ($parent == 'nav') {
                $child = 'nav';
            }
            // Create parent node
            $nav = new Child($parent);
            if (null !== $this->indent) {
                $nav->setIndent(str_repeat($this->indent, $depth));
            }
            // Set parent attributes if they exist
            if (isset($this->config['parent']) && isset($this->config['parent']['id'])) {
                $nav->setAttribute('id', $this->config['parent']['id'] . '-' . $this->parentLevel);
            }
            if (isset($this->config['parent']) && isset($this->config['parent']['class'])) {
                $nav->setAttribute('class', $this->config['parent']['class'] . '-' . $depth);
            }
            if (isset($this->config['parent']['attributes'])) {
                foreach ($this->config['parent']['attributes'] as $attrib => $value) {
                    $nav->setAttribute($attrib, $value);
                }
            }
        }
        $this->parentLevel++;
        $depth++;
        // Recursively loop through the nodes
        foreach ($tree as $node) {
            $allowed = true;
            if (isset($node['acl'])) {
                if (null === $this->acl) {
                    throw new Exception('The access control object is not set.');
                }
                if (empty($this->roles)) {
                    $allowed = false;
                } else {
                    $resource   = (isset($node['acl']['resource'])) ? $node['acl']['resource'] : null;
                    $permission = (isset($node['acl']['permission'])) ? $node['acl']['permission'] : null;
                    $allowed    = ($this->aclStrict) ?
                        $this->acl->isAllowedManyStrict($this->roles, $resource, $permission) :
                        $this->acl->isAllowedMany($this->roles, $resource, $permission);
                }
            }
            if (($allowed) && isset($node['name']) && isset($node['href'])) {
                // Create child node and child link node
                $a = new Child('a', $node['name']);
                if ((substr($node['href'], 0, 1) == '#') || (substr($node['href'], -1) == '#') ||
                    (substr($node['href'], 0, 4) == 'http') || (substr($node['href'], 0, 7) == 'mailto:')) {
                    $href = $node['href'];
                } else if (substr($node['href'], 0, 1) == '/') {
                    $href = $this->baseUrl . $node['href'];
                } else {
                    if (substr($parentHref, -1) == '/') {
                        $href = $parentHref . $node['href'];
                    } else {
                        $href = $parentHref . '/' . $node['href'];
                    }
                }
                $a->setAttribute('href', $href);
                if (($this->returnFalse) && (($href == '#') || (substr($href, -1) == '#'))) {
                    $a->setAttribute('onclick', 'return false;');
                }
                $url = $_SERVER['REQUEST_URI'];
                if (strpos($url, '?') !== false) {
                    $url = substr($url, strpos($url, '?'));
                }
                $linkClass = null;
                if ($href == $url) {
                    if (isset($this->config['on'])) {
                        $linkClass = $this->config['on'];
                    }
                } else {
                    if (isset($this->config['off'])) {
                        $linkClass = $this->config['off'];
                    }
                }
                // If the node has any attributes
                if (isset($node['attributes'])) {
                    foreach ($node['attributes'] as $attrib => $value) {
                        $value = (($attrib == 'class') && (null !== $linkClass)) ? $value . ' ' . $linkClass : $value;
                        $a->setAttribute($attrib, $value);
                    }
                } else if (null !== $linkClass) {
                    $a->setAttribute('class', $linkClass);
                }
                if (null !== $child) {
                    $navChild = new Child($child);
                    // Set child attributes if they exist
                    if (isset($this->config['child']) && isset($this->config['child']['id'])) {
                        $navChild->setAttribute('id', $this->config['child']['id'] . '-' . $this->childLevel);
                    }
                    if (isset($this->config['child']) && isset($this->config['child']['class'])) {
                        $navChild->setAttribute('class', $this->config['child']['class'] . '-' . ($depth - 1));
                    }
                    if (isset($this->config['child']['attributes'])) {
                        foreach ($this->config['child']['attributes'] as $attrib => $value) {
                            $navChild->setAttribute($attrib, $value);
                        }
                    }
                    // Add link node
                    $navChild->addChild($a);
                    $this->childLevel++;
                    // If there are children, loop through and add them
                    if (isset($node['children']) && is_array($node['children']) && (count($node['children']) > 0)) {
                        $childrenAllowed = true;
                        // Check if the children are allowed
                        $i = 0;
                        foreach ($node['children'] as $nodeChild) {
                            if (isset($nodeChild['acl'])) {
                                if (null === $this->acl) {
                                    throw new Exception('The access control object is not set.');
                                }
                                if (empty($this->roles)) {
                                    $childrenAllowed = false;
                                } else {
                                    $resource   = (isset($nodeChild['acl']['resource'])) ? $nodeChild['acl']['resource'] : null;
                                    $permission = (isset($nodeChild['acl']['permission'])) ? $nodeChild['acl']['permission'] : null;
                                    $method     = ($this->aclStrict) ? 'isAllowedManyStrict' : 'isAllowedMany';
                                    if (!($this->acl->{$method}($this->roles, $resource, $permission))) {
                                        $i++;
                                    }
                                }
                            }
                        }
                        if ($i == count($node['children'])) {
                            $childrenAllowed = false;
                        }
                        if ($childrenAllowed) {
                            $nextChild = $this->traverse($node['children'], $depth, $href);
                            if (($nextChild->hasChildren()) || (null !== $nextChild->getNodeValue())) {
                                $navChild->addChild($nextChild);
                            }
                        }
                    }
                    // Add child node
                    $nav->addChild($navChild);
                } else {
                    $nav->addChild($a);
                }
            }
        }
        return $nav;
    }
}