modxcms/revolution

View on GitHub
manager/controllers/default/header.php

Summary

Maintainability
C
1 day
Test Coverage
<?php
/*
 * This file is part of MODX Revolution.
 *
 * Copyright (c) MODX, LLC. All Rights Reserved.
 *
 * For complete copyright and license information, see the COPYRIGHT and LICENSE
 * files found in the top-level directory of this distribution.
 */

/**
 * Loads the main structure
 *
 * @see modManagerController::getHeader
 * @see modManagerController::loadController
 *
 * @var modX $modx
 * @var modManagerController $this
 *
 * @package modx
 * @subpackage manager.controllers
 */

class TopMenu
{
    /**
     * @var modManagerController
     */
    public $controller;
    /**
     * @var modX
     */
    public $modx;
    /**
     * The current menu HTML output
     *
     * @var string
     */
    protected $output = '';
    /**
     * Whether or not to display menus description
     *
     * @var bool
     */
    protected $showDescriptions = true;
    /**
     * Current menu index
     *
     * @var int
     */
    protected $order = 0;
    /**
     * Current children menu index
     *
     * @var int
     */
    protected $childrenCt = 0;

    public function __construct(modManagerController &$controller)
    {
        $this->controller =& $controller;
        $this->modx =& $controller->modx;
        $this->showDescriptions = (boolean) $this->modx->getOption('topmenu_show_descriptions', null, true);
        $this->subItemsMax = abs((int) $this->modx->getOption('topmenu_subitems_max', null, 0, true));
    }

    /**
     * Build the top menu
     *
     * @return void
     */
    public function render()
    {
        // First assign most variables so they could be used within menus
        $this->setPlaceholders();

        // Then process menu "containers"
        $mainNav = $this->modx->smarty->getTemplateVars('navb');
        if (empty($mainNav)) {
            $this->buildMenu(
                $this->modx->getOption('main_nav_parent', null, 'topnav', true),
                'navb'
            );
        }
        $userNav = $this->modx->smarty->getTemplateVars('userNav');
        if (empty($userNav)) {
            $this->buildMenu(
                $this->modx->getOption('user_nav_parent', null, 'usernav', true),
                'userNav'
            );
        }
    }

    /**
     * Set a bunch of placeholders to be used within Smarty templates
     *
     * @return void
     */
    public function setPlaceholders()
    {
        $username = '';
        if ($this->modx->getOption('manager_use_fullname') == true) {
            $userProfile = $this->modx->user->getOne('Profile');
            $username = $userProfile->get('fullname');
        }

        if (empty($username)) {
            $username = $this->modx->getLoginUserName();
        }
        $placeholders = array(
            'username' => $username,
            'userImage' => $this->getUserImage(),
        );

        $this->controller->setPlaceholders($placeholders);
    }

    /**
     * Retrieve/compute the user picture profile
     *
     * @return string The HTML output
     */
    public function getUserImage()
    {
        // Default to FontAwesome
        $output = '<i class="icon icon-user icon-large"></i>&nbsp;';
        $img = $this->modx->user->getPhoto(128, 128);

        if (!empty($img)) {
            $output = '<img src="' . $img . '" />';
        }

        return $output;
    }

    /**
     * Build the requested menu "container" and set it as a placeholder
     *
     * @param string $name The container name (topnav, usernav)
     * @param string $placeholder The placeholder to display the built menu to
     *
     * @return void
     */
    public function buildMenu($name, $placeholder)
    {
        if (!$placeholder) {
            $placeholder = $name;
        }

        // Grab the menus to process
        $menus = $this->getCache($name);
        // Iterate
        foreach ($menus as $menu) {
            $this->childrenCt = 0;

            if (!$this->hasPermission($menu['permissions'])) {
                continue;
            }

            $description = '';
            if ($this->showDescriptions && !empty($menu['description'])) {
                $description = '<span class="description">'.$menu['description'].'</span>'."\n";
            }

            $label = $menu['text'];
            $title = ' title="' . $menu['description'] .'"';
            $icon = false;
            if (!empty($menu['icon'])) {
                $icon = true;
                // Use the icon as label
                $label = $menu['icon'];
                // Reset the description (which is set as text in $title)
                $description = '';
            }

            $top = (!empty($menu['children'])) ? ' class="top"' : '';
            $menuTpl = '<li id="limenu-'.$menu['id'].'"'.$top.'>'."\n";

            if (!empty($menu['action'])) {
                if ($menu['namespace'] != 'core') {
                    // Handle the namespace
                    $menu['action'] .= '&namespace='.$menu['namespace'];
                }
                if (!$icon) {
                    // No icon, no title property
                    $title = '';
                }
                $onclick = (!empty($menu['handler'])) ? ' onclick="'.str_replace('"','\'',$menu['handler']).'"' : '';
                $menuTpl .= '<a href="?a='.$menu['action'].$menu['params'].'"'.( $top ? ' class="top-link"': '' ).$onclick.$title.'>'.$label.$description.'</a>'."\n";
            } elseif (!empty($menu['handler'])) {
                $menuTpl .= '<a href="javascript:;" onclick="'.str_replace('"','\'',$menu['handler']).'">'.$label.'</a>'."\n";
            } else {
                $menuTpl .= '<a href="javascript:;">'.$label.'</a>'."\n";
            }

            if (!empty($menu['children'])) {
                $menuTpl .= '<ul class="modx-subnav">'."\n";
                $this->processSubMenus($menuTpl, $menu['children'], $this->subItemsMax);
                $menuTpl .= '</ul>'."\n";
            }
            $menuTpl .= '</li>'."\n";

            /* if has no permissable children, and is not clickable, hide top menu item */
            if (!empty($this->childrenCt) || !empty($menu['action']) || !empty($menu['handler'])) {
                $this->output .= $menuTpl;
            }
            $this->order++;
        }

        //$this->cleanEmptySubMenus();
        $this->controller->setPlaceholder($placeholder, $this->output);
        $this->resetCounters();
    }

    /**
     * Retrieve the menus for the given "container"
     *
     * @param string $name
     *
     * @return array
     */
    protected function getCache($name)
    {
        $key = $this->getCacheKey($name);

        $menus = $this->modx->cacheManager->get($key, array(
            xPDO::OPT_CACHE_KEY => $this->modx->getOption('cache_menu_key', null, 'menu'),
            xPDO::OPT_CACHE_HANDLER => $this->modx->getOption(
                'cache_menu_handler',
                null,
                $this->modx->getOption(xPDO::OPT_CACHE_HANDLER)
            ),
            xPDO::OPT_CACHE_FORMAT => (integer) $this->modx->getOption(
                'cache_menu_format',
                null,
                $this->modx->getOption(xPDO::OPT_CACHE_FORMAT, null, xPDOCacheManager::CACHE_PHP)
            ),
        ));

        if ($menus == null || !is_array($menus)) {
            /** @var modMenu $menu */
            $menu = $this->modx->newObject('modMenu');
            $menus = $menu->rebuildCache($name);
            unset($menu);
        }

        return $menus;
    }

    /**
     * Compute the cache key for the given menu "container"
     *
     * @param string $name
     *
     * @return string
     */
    protected function getCacheKey($name)
    {
        return "menus/{$name}/" . $this->modx->getOption(
            'manager_language',
            null,
            $this->modx->getOption('cultureKey', null, 'en')
        );
    }

    /**
     * Reset menu HTML output & indexes counters
     *
     * @return void
     */
    protected function resetCounters()
    {
        $this->output = '';
        $this->order = 0;
        $this->childrenCt = 0;
    }

    /**
     * Check if the current user is allowed to view the menu record
     *
     * @param string $perms
     *
     * @return bool
     */
    public function hasPermission($perms)
    {
        if (empty($perms)) {
            return true;
        }
        $permissions = array();
        $exploded = explode(',', $perms);
        foreach ($exploded as $permission) {
            $permissions[trim($permission)] = true;
        }

        return $this->modx->hasPermission($permissions);
    }

    /**
     * Process the given sub menus
     *
     * @param string $output The existing menu HTML "output"
     * @param array $menus The sub menus to process
     *
     * @return void
     */
    public function processSubMenus(&$output, array $menus = array(), $maxItems = false)
    {
        //$output .= '<ul class="modx-subnav">'."\n";
        $moreMenu = '';
        if ($maxItems && count($menus) > $maxItems) {
            $moreMenu = array_slice($menus, $maxItems);
            $menus = array_slice($menus, 0, $maxItems);
        }

        foreach ($menus as $menu) {
            if (!$this->hasPermission($menu['permissions'])) {
                continue;
            }
            $sub = (!empty($menu['children'])) ? ' class="sub"' : '';
            $smTpl = '<li id="'.$menu['id'].'"'.$sub.'>'."\n";

            $description = '';
            if ($this->showDescriptions && !empty($menu['description'])) {
                $description = '<span class="description">'.$menu['description'].'</span>'."\n";
            }

            $attributes = '';
            if (!empty($menu['action'])) {
                if ($menu['namespace'] != 'core') {
                    $menu['action'] .= '&namespace='.$menu['namespace'];
                }
                $attributes = ' href="?a='.$menu['action'].$menu['params'].'"';
            }
            if (!empty($menu['handler'])) {
                $attributes .= ' onclick="{literal} '.str_replace('"','\'',$menu['handler']).'{/literal} "';
            }
            $smTpl .= '<a'.$attributes.'>'.$menu['text'].$description.'</a>'."\n";

            if (!empty($menu['children'])) {
                $smTpl .= '<ul class="modx-subsubnav">'."\n";
                $this->processSubMenus($smTpl, $menu['children'], $this->subItemsMax);
                $smTpl .= '</ul>'."\n";
            }
            $smTpl .= '</li>';
            $output .= $smTpl;
            $this->childrenCt++;
        }

        if (!empty($moreMenu)) {
            $output .= '<li class="sub"><a href="#">...</a>'."\n";
            $output .= '<ul class="modx-subsubnav more">'."\n";
            $this->processSubMenus($output, $moreMenu, $this->subItemsMax);
            $output .= '</ul>'."\n";
        }

        //$output .= '</ul>'."\n";
    }

    /**
     * Clean "orphan" sub menus
     *
     * @return void
     */
    public function cleanEmptySubMenus()
    {
        $emptySub = '<ul class="modx-subsubnav">'."\n".'</ul>'."\n";

        $this->output = str_replace($emptySub, '', $this->output);
    }
}

// Set Smarty placeholder to display search bar, if appropriate
$this->setPlaceholder('_search', $modx->hasPermission('search'));

$menu = new TopMenu($this);
$menu->render();