<?php
/**
 * PHP Server Monitor
 * Monitor your servers and websites.
 *
 * This file is part of PHP Server Monitor.
 * PHP Server Monitor is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * PHP Server Monitor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with PHP Server Monitor.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @package     phpservermon
 * @author      Pepijn Over <pep@mailbox.org>
 * @copyright   Copyright (c) 2008-2017 Pepijn Over <pep@mailbox.org>
 * @license     http://www.gnu.org/licenses/gpl.txt GNU GPL v3
 * @version     Release: v3.2.0
 * @link        http://www.phpservermonitor.org/
 **/

namespace psm\Module;
use psm\Service\Database;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\DependencyInjection\ContainerAware;

abstract class AbstractController extends ContainerAware implements ControllerInterface {

	/**
	 * Current action
	 * @var string $action
	 */
	protected $action;

	/**
	 * Default action
	 * @var string $action_default
	 * @see setActions()
	 */
	protected $action_default;

	/**
	 * Actions available for this module
	 * @var array $actions
	 * @see setActions()
	 * @see getAction()
	 */
	protected $actions = array();

	/**
	 * Add footer to page?
	 * @var boolean $add_footer
	 * @see addFooter()
	 */
	protected $add_footer = true;

	/**
	 * Add menu to page?
	 * @var boolean $add_menu
	 * @see addMenu()
	 */
	protected $add_menu = true;

	/**
	 * @var string $csrf_key
	 */
	protected $csrf_key;

	/**
	 * Messages to show the user
	 * @var array $messages
	 * @see addMessage()
	 */
	protected $messages = array();

	/**
	 * Sidebar to add
	 * @var \psm\Util\Module\Sidebar $sidebar
	 */
	protected $sidebar;

	/**
	 * array of Modal to add
	 * @var \psm\Util\Module\ModalInterface[] $modal
	 */
	protected $modal = array();

	/**
	 * html code of header accessories
	 * @var string $header_accessories
	 */
	protected $header_accessories;

	/**
	 * Database object
	 * @var \psm\Service\Database $db
	 */
	protected $db;

	/**
	 * Twig object
	 * @var \Twig_Environment $twig
	 */
	protected $twig;

	/**
	 * User service
	 * @var \psm\Service\User $user
	 */
	protected $user;

	/**
	 * Required user level for this module
	 * @var int $user_level_required
	 * @see setMinUserLevelRequired()
	 */
	protected $user_level_required = PSM_USER_USER;

	/**
	 * Required user level for certain actions
	 * @var int $user_level_required_actions
	 * @see setMinUserLevelRequiredForAction()
	 */
	protected $user_level_required_actions = array();

	/*
	 * Required using black background layout
	 * @var boolean $black_background
	 */
	protected $black_background = false;

	/**
	 * XHR mode?
	 * @var boolean $xhr
	 * @see isXHR()
	 */
	protected $xhr = false;

	function __construct(Database $db, \Twig_Environment $twig) {
		$this->db = $db;
		$this->twig = $twig;
	}

	/**
	 * Run the controller.
	 *
	 * @param string $action if NULL, the action will be retrieved from user input (GET/POST)
	 * @return \Symfony\Component\HttpFoundation\Response
	 */
	public function run($action = null) {
		if($action === null) {
			$action = psm_GET('action', psm_POST('action', $this->action_default));
		}
		$this->xhr = (bool) psm_GET('xhr', psm_POST('xhr', false));

		if(!in_array($action, $this->actions) || !($result = $this->runAction($action))) {
			$result = $this->runAction($this->action_default);
		}

		if($result instanceof Response) {
			return $result;
		}

		// no response returned from execute, create regular HTML
		return $this->createHTML($result);
	}

	/**
	 * Run a specified action
	 *
	 * For it to run, the "execute$action" method must exist.
	 * @param string $action
	 * @return mixed FALSE when action couldnt be initialized, response otherwise
	 */
	protected function runAction($action) {
		if(isset($this->user_level_required_actions[$action])) {
			if($this->getUser()->getUserLevel() > $this->user_level_required_actions[$action]) {
				// user is not allowed to access this action..
				return false;
			}
		}
		$method = 'execute' . ucfirst($action);
		if(method_exists($this, $method)) {
			$this->action = $action;
			$result = $this->$method();
			// if result from execute is null, no return value given so return true to indicate a successful execute
			return ($result === null) ? true : $result;
		}
		return false;
	}

	/**
	 * Create the HTML code for the module.
	 *
	 * If XHR is on, no main template will be added.
	 *
	 * @param string $html HTML code to add to the main body
	 * @return \Symfony\Component\HttpFoundation\Response
	 */
	protected function createHTML($html = null) {
		if(!$this->xhr) {
			// in XHR mode, we will not add the main template
			$tpl_data = array(
				'title' => strtoupper(psm_get_lang('system', 'title')),
				'label_back_to_top' => psm_get_lang('system', 'back_to_top'),
				'add_footer' => $this->add_footer,
				'version' => 'v' . PSM_VERSION,
				'messages' => $this->getMessages(),
				'html_content' => $html,
			);

			// add menu to page?
			if($this->add_menu) {
				$tpl_data['html_menu'] = $this->createHTMLMenu();
			}
			// add header accessories to page ?
			if($this->header_accessories) {
				$tpl_data['header_accessories'] = $this->header_accessories;
			}
			// add modal dialog to page ?
			if(sizeof($this->modal)) {
				$html_modal = '';
				foreach($this->modal as $modal) {
					$html_modal .= $modal->createHTML();
				}
				$tpl_data['html_modal'] = $html_modal;
			}
			// add sidebar to page?
			if($this->sidebar !== null) {
				$tpl_data['html_sidebar'] = $this->sidebar->createHTML();
			}

			if(psm_update_available()) {
				$tpl_data['update_available'] = str_replace('{version}', 'v'.psm_get_conf('version_update_check'), psm_get_lang('system', 'update_available'));
			}

			if($this->black_background) {
				$tpl_data['body_class'] = 'black_background';
			}
			$html = $this->twig->render('main/body.tpl.html', $tpl_data);
		}

		$response = new Response($html);

		return $response;
	}

	/**
	 * Create HTML code for the menu
	 * @return string
	 */
	protected function createHTMLMenu() {
		$ulvl = $this->getUser()->getUserLevel();

		$tpl_data = array(
			'label_help' => psm_get_lang('menu', 'help'),
			'label_profile' => psm_get_lang('users', 'profile'),
			'label_logout' => psm_get_lang('login', 'logout'),
			'url_profile' => psm_build_url(array('mod' => 'user_profile')),
			'url_logout' => psm_build_url(array('logout' => 1)),
		);

		switch($ulvl) {
			case PSM_USER_ADMIN:
				$items = array('server_status', 'server', 'server_log', 'user', 'config', 'server_update');
				break;
			case PSM_USER_USER:
				$items = array('server_status', 'server', 'server_log', 'server_update');
				break;
			default:
				$items = array();
				break;
		}
		$tpl_data['menu'] = array();
		foreach($items as $key) {
			$tpl_data['menu'][] = array(
				'active' => ($key == psm_GET('mod')) ? 'active' : '',
				'url' => psm_build_url(array('mod' => $key)),
				'label' => psm_get_lang('menu', $key),
			);
		}

		if($ulvl != PSM_USER_ANONYMOUS) {
			$user = $this->getUser()->getUser();
			$tpl_data['label_usermenu'] = str_replace(
				'%user_name%',
				$user->name,
				psm_get_lang('login', 'welcome_usermenu')
			);
		}
		return $this->twig->render('main/menu.tpl.html', $tpl_data);
	}

	/**
	 * Hide or show the footer of the page
	 * @param boolean $value
	 */
	protected function addFooter($value) {
		$this->add_footer = $value;
	}

	/**
	 * Hide or show the menu of the page
	 * @param boolean $value
	 */
	protected function addMenu($value) {
		$this->add_menu = $value;
	}

	/**
	 * Set actions available
	 * @param string|array $actions
	 * @param string $default default action
	 * @param boolean $append if TRUE, the actions will be added to the current actions
	 * @return psm\Module\AbstractModule
	 * @see getAction()
	 */
	protected function setActions($actions, $default = null, $append = true) {
		if(!is_array($actions)) {
			$actions = array($actions);
		}
		if($append) {
			$this->actions = array_merge($actions);
		} else {
			$this->actions = $actions;
		}
		if($default !== null) {
			$this->action_default = $default;
		}
		return $this;
	}

	/**
	 * Get the current action
	 * @return string
	 * @see setActions()
	 */
	public function getAction() {
		return $this->action;
	}

	/**
	 * Add one or multiple message to the stack to be displayed to the user
	 * @param string|array $msg
	 * @param string $shortcode info/success/warning/error
	 * @return \psm\Module\ControllerInterface
	 * @see getMessages()
	 */
	public function addMessage($msg, $shortcode = 'info') {
		if(!is_array($msg)) {
			$msg = array($msg);
		}
		switch($shortcode) {
			case 'error':
				$icon = 'exclamation-sign';
				break;
			case 'success':
				$icon = 'ok-sign';
				break;
			case 'warning':
				$icon = 'question-sign';
				break;
			default:
				$icon = 'info-sign';
				break;
		}

		foreach($msg as $m) {
			$this->messages[] = array(
				'message' => $m,
				'shortcode' => $shortcode,
				'icon' => $icon,
			);
		}
		return $this;
	}

	/**
	 * Get all messages (and optionally clear them)
	 * @param boolean $clear
	 * @return array
	 * @see addMessage()
	 */
	public function getMessages($clear = true) {
		$msgs = $this->messages;
		if($clear) {
			$this->messages = array();
		}
		return $msgs;
	}

	/**
	 * Set the minimum required user level for this controller
	 * @param int $level
	 * @return \psm\Module\AbstractController
	 */
	public function setMinUserLevelRequired($level) {
		$this->user_level_required = intval($level);
		return $this;
	}

	/**
	 * Get the minimum required user level for this controller
	 * @return int
	 */
	public function getMinUserLevelRequired() {
		return $this->user_level_required;
	}

	/**
	 * Set the minimum required user level for a certain action.
	 *
	 * Use this only if one of the access is more restricted than the entire controller
	 * @param int $level
	 * @param string|array $actions one or more actions to set this level for
	 * @return \psm\Module\AbstractController
	 * @see setMinUserLevelRequired()
	 */
	public function setMinUserLevelRequiredForAction($level, $actions) {
		if(!is_array($actions)) {
			$actions = array($actions);
		}
		foreach($actions as $action) {
			$this->user_level_required_actions[$action] = intval($level);
		}
		return $this;
	}

	/**
	 * Add a sidebar to the page
	 * @param \psm\Util\Module\SidebarInterface $sidebar
	 * @return \psm\Module\ControllerInterface
	 */
	public function setSidebar(\psm\Util\Module\SidebarInterface $sidebar) {
		$this->sidebar = $sidebar;
		return $this;
	}

	/**
	 * Add a modal dialog to the page
	 * @param \psm\Util\Module\ModalInterface $modal
	 * @return \psm\Module\ControllerInterface
	 */
	public function addModal(\psm\Util\Module\ModalInterface $modal) {
		$this->modal[$modal->getModalID()] = $modal;
		return $this;
	}

	/**
	 * Set the html code of the header accessories
	 * @param string $html
	 */
	public function setHeaderAccessories($html) {
		$this->header_accessories = $html;
	}

	/**
	 * Check if XHR is on
	 * @return boolean
	 */
	public function isXHR() {
		return $this->xhr;
	}

	/**
	 * Get user service
	 * @return \psm\Service\User
	 */
	public function getUser() {
		return $this->container->get('user');
	}

	/**
	 * Get custom key for CSRF validation
	 * @return string
	 */
	public function getCSRFKey() {
		return $this->csrf_key;
	}

	/**
	 * Set CSRF key for validation
	 * @param string $key
	 * @return \psm\Module\ControllerInterface
	 */
	protected function setCSRFKey($key) {
		$this->csrf_key = $key;
		$this->twig->addGlobal('csrf_key', $key);
		return $this;
	}
}
