<commit_before><?php
/*
 * This file is part of the vxPHP/vxWeb framework
 *
 * (c) <NAME> <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */


namespace vxPHP\Webpage;

use vxPHP\Http\Request;
use vxPHP\Routing\Router;
use vxPHP\Routing\Route;
use vxPHP\Webpage\Exception\MenuGeneratorException;
use vxPHP\Webpage\Menu\Menu;
use vxPHP\Webpage\MenuEntry\MenuEntry;
use vxPHP\Application\Application;
use vxPHP\Application\Config;

/**
 * Wrapper class for rendering a menu
 *
 * @author <NAME>
 *
 * @version 0.6.2, 2018-07-07
 *
 * @throws MenuGeneratorException
 */
class MenuGenerator {

	/**
	 * flag indicating that currently selected menu entries still stay
	 * "active", i.e. still render as anchor elements which can be
	 * clicked
	 * 
	 * @var boolean
	 */
	protected static $forceActiveMenu;

	/**
	 * cache holding already parsed menus in case they are rendered
	 * more than once
	 * 
	 * @var Menu[]
	 */
	protected static $primedMenus = [];

	/**
	 * @var Route
	 */
	protected $route;

	/**
	 * indicates whether mod-rewrite like URLs are to be used
	 * 
	 * @var boolean
	 */
	protected $useNiceUris;

	/**
	 * path segments matched against menu structure to evaluate the
	 * active menu entry
	 * 
	 * @var array
	 */
	protected $pathSegments;

	/**
	 * the generated menu instance
	 * 
	 * @var Menu
	 */
	protected $menu;

	/**
	 * class name of decorator to be used
	 * 
	 * @var string
	 */
	protected $decorator;

	/**
	 * unique id of menu
	 * 
	 * @var string
	 */
	protected $id;

	/**
	 * level of menu in menu hierarchy
	 * 
	 * @var integer
	 */
	protected $level;

	/**
	 * additional parameters passed to menu renderer
	 * 
	 * @var array
	 */
	protected $renderArgs;

	/**
	 * class used for menu and menu entry authentication
	 *
	 * @var MenuAuthenticatorInterface
	 */
	protected static $authenticator;

    /**
     * sets active menu entries, allows addition of dynamic entries and
     * prints level $level of a menu, identified by $id
     *
     * $decorator identifies a decorator class - MenuDecorator{$decorator}
     * $renderArgs are additional parameters passed to Menu::render()
     *
     * @param string $id
     * @param bool $level (if NULL, the full menu tree is printed)
     * @param bool $forceActiveMenu
     * @param string $decorator
     * @param mixed $renderArgs
     *
     * @throws MenuGeneratorException
     * @throws \vxPHP\Application\Exception\ApplicationException
     */
	public function __construct($id = null, $level = false, $forceActiveMenu = null, $decorator = null, $renderArgs = null) {

		$application = Application::getInstance();

		$config = $application->getConfig();
		$this->useNiceUris = $application->hasNiceUris();

		$this->route = $application->getCurrentRoute();

		if(is_null($this->route)) {
			$this->route = $application->getRouter()->getRouteFromPathInfo(Request::createFromGlobals());
		}

		if(empty($id)) {
            if (array_key_exists(Menu::DEFAULT_ID, $config->menus)) {
                $id = Menu::DEFAULT_ID;
            } else {
                $id = array_shift(array_keys($config->menus));
            }
        }

		if(
			!isset($config->menus[$id]) ||
			!count($config->menus[$id]->getEntries()) &&
			$config->menus[$id]->getType() == 'static'
		) {
			throw new MenuGeneratorException("Menu '" .$id. "' not found or empty.");
		}

		$this->menu = $config->menus[$id];

		$this->id = $id;
		$this->level = $level;
		$this->decorator = $decorator;
		$this->renderArgs = $renderArgs ?? [];

		// if $forceActiveMenu was initialized before, it will not be overwritten

		if(is_null(self::$forceActiveMenu)) {
			self::$forceActiveMenu	= (boolean) $forceActiveMenu;
		}

	}

    /**
     * convenience method to allow chaining
     *
     * @param string $id
     * @param bool $level (if NULL, the full menu tree is printed)
     * @param bool $forceActiveMenu
     * @param string $decorator
     * @param mixed $renderArgs
     * @return MenuGenerator
     * @throws MenuGeneratorException
     * @throws \vxPHP\Application\Exception\ApplicationException
     */
	public static function create($id = null, $level = false, $forceActiveMenu = null, $decorator = null, $renderArgs = null) {

		return new static($id, $level, $forceActiveMenu, $decorator, $renderArgs);

	}

	/**
	 * activates or deactivates
	 * "active" menu entries (selected entries are clickable)
	 *
	 * @param boolean $state
	 */

	public static function setForceActiveMenu($state) {

		self::$forceActiveMenu = (boolean) $state;

	}
	
	/**
	 * set the authenticator class which will be used to authenticate
	 * the menu and menu entries
	 * 
	 * @param MenuAuthenticatorInterface $authenticator
	 */
	public static function setMenuAuthenticator(MenuAuthenticatorInterface $authenticator) {

		self::$authenticator = $authenticator;

	}

    /**
     * render menu markup
     *
     * @return string
     * @throws \vxPHP\Application\Exception\ApplicationException
     */
	public function render() {

		// check authentication

		if(!$this->authenticateMenu($this->menu)) {
			return '';
		}

		$request = Request::createFromGlobals();
		
		// if menu has not been prepared yet, do it now (caching avoids re-parsing for submenus)

		if(!in_array($this->menu, self::$primedMenus, TRUE)) {

			// clear selected menu entries (which remain in the session)

			$this->clearSelectedMenuEntries($this->menu);

			// walk entire menu and add dynamic entries where necessary
			
			$this->completeMenu($this->menu);
			
			// prepare path segments to identify active menu entries

			$this->pathSegments = explode('/', trim($request->getPathInfo(), '/'));

			// skip script name

			if($this->useNiceUris && basename($request->getScriptName()) != 'index.php') {
				array_shift($this->pathSegments);
			}

			// skip locale if one found

			if(count($this->pathSegments) && Application::getInstance()->hasLocale($this->pathSegments[0])) {
				array_shift($this->pathSegments);
			}

			// walk tree until an active entry is reached

			$this->walkMenuTree($this->menu, $this->pathSegments[0] === '' ? explode('/', $this->route->getPath()) : $this->pathSegments);
<commit_msg>Bugfix: Menu generation failed with a non standard script name and empty path segments<commit_after><?php
/*
 * This file is part of the vxPHP/vxWeb framework
 *
 * (c) <NAME> <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */


namespace vxPHP\Webpage;

use vxPHP\Http\Request;
use vxPHP\Routing\Router;
use vxPHP\Routing\Route;
use vxPHP\Webpage\Exception\MenuGeneratorException;
use vxPHP\Webpage\Menu\Menu;
use vxPHP\Webpage\MenuEntry\MenuEntry;
use vxPHP\Application\Application;
use vxPHP\Application\Config;

/**
 * Wrapper class for rendering a menu
 *
 * @author <NAME>
 *
 * @version 0.6.3, 2018-09-28
 *
 * @throws MenuGeneratorException
 */
class MenuGenerator {

	/**
	 * flag indicating that currently selected menu entries still stay
	 * "active", i.e. still render as anchor elements which can be
	 * clicked
	 * 
	 * @var boolean
	 */
	protected static $forceActiveMenu;

	/**
	 * cache holding already parsed menus in case they are rendered
	 * more than once
	 * 
	 * @var Menu[]
	 */
	protected static $primedMenus = [];

	/**
	 * @var Route
	 */
	protected $route;

	/**
	 * indicates whether mod-rewrite like URLs are to be used
	 * 
	 * @var boolean
	 */
	protected $useNiceUris;

	/**
	 * path segments matched against menu structure to evaluate the
	 * active menu entry
	 * 
	 * @var array
	 */
	protected $pathSegments;

	/**
	 * the generated menu instance
	 * 
	 * @var Menu
	 */
	protected $menu;

	/**
	 * class name of decorator to be used
	 * 
	 * @var string
	 */
	protected $decorator;

	/**
	 * unique id of menu
	 * 
	 * @var string
	 */
	protected $id;

	/**
	 * level of menu in menu hierarchy
	 * 
	 * @var integer
	 */
	protected $level;

	/**
	 * additional parameters passed to menu renderer
	 * 
	 * @var array
	 */
	protected $renderArgs;

	/**
	 * class used for menu and menu entry authentication
	 *
	 * @var MenuAuthenticatorInterface
	 */
	protected static $authenticator;

    /**
     * sets active menu entries, allows addition of dynamic entries and
     * prints level $level of a menu, identified by $id
     *
     * $decorator identifies a decorator class - MenuDecorator{$decorator}
     * $renderArgs are additional parameters passed to Menu::render()
     *
     * @param string $id
     * @param bool $level (if NULL, the full menu tree is printed)
     * @param bool $forceActiveMenu
     * @param string $decorator
     * @param mixed $renderArgs
     *
     * @throws MenuGeneratorException
     * @throws \vxPHP\Application\Exception\ApplicationException
     */
	public function __construct($id = null, $level = false, $forceActiveMenu = null, $decorator = null, $renderArgs = null) {

		$application = Application::getInstance();

		$config = $application->getConfig();
		$this->useNiceUris = $application->hasNiceUris();

		$this->route = $application->getCurrentRoute();

		if(is_null($this->route)) {
			$this->route = $application->getRouter()->getRouteFromPathInfo(Request::createFromGlobals());
		}

		if(empty($id)) {
            if (array_key_exists(Menu::DEFAULT_ID, $config->menus)) {
                $id = Menu::DEFAULT_ID;
            } else {
                $id = array_shift(array_keys($config->menus));
            }
        }

		if(
			!isset($config->menus[$id]) ||
			!count($config->menus[$id]->getEntries()) &&
			$config->menus[$id]->getType() == 'static'
		) {
			throw new MenuGeneratorException("Menu '" .$id. "' not found or empty.");
		}

		$this->menu = $config->menus[$id];

		$this->id = $id;
		$this->level = $level;
		$this->decorator = $decorator;
		$this->renderArgs = $renderArgs ?? [];

		// if $forceActiveMenu was initialized before, it will not be overwritten

		if(is_null(self::$forceActiveMenu)) {
			self::$forceActiveMenu	= (boolean) $forceActiveMenu;
		}

	}

    /**
     * convenience method to allow chaining
     *
     * @param string $id
     * @param bool $level (if NULL, the full menu tree is printed)
     * @param bool $forceActiveMenu
     * @param string $decorator
     * @param mixed $renderArgs
     * @return MenuGenerator
     * @throws MenuGeneratorException
     * @throws \vxPHP\Application\Exception\ApplicationException
     */
	public static function create($id = null, $level = false, $forceActiveMenu = null, $decorator = null, $renderArgs = null) {

		return new static($id, $level, $forceActiveMenu, $decorator, $renderArgs);

	}

	/**
	 * activates or deactivates
	 * "active" menu entries (selected entries are clickable)
	 *
	 * @param boolean $state
	 */

	public static function setForceActiveMenu($state) {

		self::$forceActiveMenu = (boolean) $state;

	}
	
	/**
	 * set the authenticator class which will be used to authenticate
	 * the menu and menu entries
	 * 
	 * @param MenuAuthenticatorInterface $authenticator
	 */
	public static function setMenuAuthenticator(MenuAuthenticatorInterface $authenticator) {

		self::$authenticator = $authenticator;

	}

    /**
     * render menu markup
     *
     * @return string
     * @throws \vxPHP\Application\Exception\ApplicationException
     */
	public function render() {

		// check authentication

		if(!$this->authenticateMenu($this->menu)) {
			return '';
		}

		$request = Request::createFromGlobals();
		
		// if menu has not been prepared yet, do it now (caching avoids re-parsing for submenus)

		if(!in_array($this->menu, self::$primedMenus, TRUE)) {

			// clear selected menu entries (which remain in the session)

			$this->clearSelectedMenuEntries($this->menu);

			// walk entire menu and add dynamic entries where necessary
			
			$this->completeMenu($this->menu);
			
			// prepare path segments to identify active menu entries

			$this->pathSegments = explode('/', trim($request->getPathInfo(), '/'));

			// skip script name

			if($this->useNiceUris && basename($request->getScriptName()) != 'index.php') {
				array_shift($this->pathSegments);
			}

			// skip locale if one found

			if(count($this->pathSegments) && Application::getInstance()->hasLocale($this->pathSegments[0])) {
				array_shift($this->pathSegments);
			}

			// walk tree until an active entry is reached

			$this->walkMenuTree($this->menu, (!count($this->pathSegments) || $this->pathSegments[0] === '') ? explode('/', $this->route->getPath()) : $this->pathSegments);

{'fraction_non_alphanumeric': 0.13164577051261706, 'fraction_numerical': 0.0020631645770512615, 'mean_word_length': 3.247724974721941, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 100, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3719227', 'n_tokens_mistral': 4097, 'n_tokens_neox': 3655, 'n_words': 1054}