Your IP : 18.118.252.210


Current Path : /home/bitrix/ext_www/crm.klimatlend.ua/bitrix/modules/report/lib/internals/
Upload File :
Current File : /home/bitrix/ext_www/crm.klimatlend.ua/bitrix/modules/report/lib/internals/controller.php

<?php

namespace Bitrix\Report\Internals;

use Bitrix\Main\Error;
use Bitrix\Main\ErrorCollection;
use Bitrix\Main\Application;
use Bitrix\Main\Context;
use Bitrix\Main\EventResult;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\Web\PostDecodeFilter;
use Bitrix\Main\Web\Json;
use Bitrix\Main\Event;

Loc::loadMessages(__FILE__);

abstract class Controller
{
	const EVENT_ON_BEFORE_ACTION = 'onBeforeAction';

	const ERROR_REQUIRED_PARAMETER = 'REPORT_CONTROLLER_22001';
	const ERROR_UNKNOWN_ACTION     = 'REPORT_CONTROLLER_22002';

	const STATUS_SUCCESS      = 'success';
	const STATUS_DENIED       = 'denied';
	const STATUS_ERROR        = 'error';
	const STATUS_NEED_AUTH    = 'need_auth';
	const STATUS_INVALID_SIGN = 'invalid_sign';
	const STATUS_RESTRICTION  = 'restriction';

	/** @var string */
	protected $action;
	/** @var  array */
	protected $actionDescription;
	/** @var  string */
	protected $realActionName;
	/** @var  ErrorCollection */
	protected $errorCollection;
	/** @var  \Bitrix\Main\HttpRequest */
	protected $request;

	/**
	 * Constructor Controller.
	 */
	public function __construct()
	{
		$this->errorCollection = new ErrorCollection;
		$this->request = Context::getCurrent()->getRequest();

		$this->init();
	}

	/**
	 * Initializes controller.
	 * This method is invoked at the end of constructor.
	 * @return void
	 */
	protected function init()
	{}

	/**
	 * Terminates controller and application.
	 * This method replaces "die()" or "exit()" and ensures life cycle of application.
	 * @return void
	 */
	protected function end()
	{
		/** @noinspection PhpUndefinedClassInspection */
		\CMain::finalActions();
		die;
	}

	/**
	 * Executes controller by specific action.
	 * This method contains all steps of life cycle controller.
	 * @return void
	 */
	public function exec()
	{
		try
		{
			//todo move in processBeforeAction()
			if($this->request->isPost())
			{
				\CUtil::jSPostUnescape();
				$this->request->addFilter(new PostDecodeFilter);
			}

			$this->resolveAction();
			$this->checkAction();

			$this->checkRequiredModules();

			if(!$this->prepareParams())
			{
				$this->sendJsonErrorResponse();
			}

			$action = $this->getAction();
			if(
				$this->processBeforeAction($action) === true &&
				$this->triggerOnBeforeAction($action) === true
			)
			{
				$this->runAction();
			}
		}
		catch(\Exception $e)
		{
			$this->runProcessingException($e);
		}
	}

	/**
	 * Triggers the event {{static::EVENT_ON_BEFORE_ACTION . $action}}.
	 * This method is invoked right before an action is executed.
	 * In case the action should not run, event handler have to return EvenResult with type EventResult::ERROR.
	 *
	 * @param string $action Action name.
	 * @return bool
	 */
	protected function triggerOnBeforeAction($action)
	{
		$event = new Event('report', static::EVENT_ON_BEFORE_ACTION . $action, array(
			'action' => $action,
			'controller' => $this,
		));
		$event->send($this);

		if($event->getResults())
		{
			foreach($event->getResults() as $eventResult)
			{
				if($eventResult->getType() != EventResult::SUCCESS)
				{
					return false;
				}
			}
		}

		return true;
	}

	/**
	 * Gets current user.
	 * @return array|bool|\CAllUser|\CUser
	 */
	protected function getUser()
	{
		global $USER;

		return $USER;
	}

	/**
	 * Sends JSON response and terminates controller.
	 * @param mixed $response
	 * @param null|array $params
	 * @return void
	 */
	protected function sendJsonResponse($response, $params = null)
	{
		if(!defined('PUBLIC_AJAX_MODE'))
		{
			define('PUBLIC_AJAX_MODE', true);
		}

		global $APPLICATION;
		$APPLICATION->restartBuffer();

		if(!empty($params['http_status']) && $params['http_status'] == 403)
		{
			header('HTTP/1.0 403 Forbidden', true, 403);
		}
		if(!empty($params['http_status']) && $params['http_status'] == 500)
		{
			header('HTTP/1.0 500 Internal Server Error', true, 500);
		}
		if(!empty($params['http_status']) && $params['http_status'] == 510)
		{
			header('HTTP/1.0 510 Not Extended', true, 510);
		}

		header('Content-Type:application/json; charset=UTF-8');
		echo Json::encode($response);

		$this->end();
	}

	/**
	 * Sends JSON response with status "error" and with errors and terminates controller.
	 * @return void
	 */
	protected function sendJsonErrorResponse()
	{
		$errors = array();
		foreach($this->getErrors() as $error)
		{
			/** @var Error $error */
			$errors[] = array(
				'message' => $error->getMessage(),
				'code' => $error->getCode(),
			);
		}
		unset($error);
		$this->sendJsonResponse(array(
			'status' => self::STATUS_ERROR,
			'errors' => $errors,
		));
	}

	/**
	 * Sends JSON response with status "denied" and terminates controller.
	 * @param string $message Message.
	 * @return void
	 */
	protected function sendJsonAccessDeniedResponse($message = '')
	{
		$this->sendJsonResponse(array(
			'status' => self::STATUS_DENIED,
			'message' => $message,
		));
	}

	/**
	 * Sends JSON response with status "invalid_sign" and terminates controller.
	 * @param string $message Message.
	 * @return void
	 */
	protected function sendJsonInvalidSignResponse($message = '')
	{
		$this->sendJsonResponse(array(
			'status' => self::STATUS_INVALID_SIGN,
			'message' => $message,
		));
	}

	/**
	 * Sends JSON response with status "success" and mixed data, and terminates controller.
	 * @param array $response Data to response.
	 * @return void
	 */
	protected function sendJsonSuccessResponse(array $response = array())
	{
		$response['status'] = self::STATUS_SUCCESS;
		$this->sendJsonResponse($response);
	}

	/**
	 * Sends response and terminates controller.
	 * Automatically restart buffer.
	 * @param mixed $response Mixed data to response.
	 * @return void
	 */
	protected function sendResponse($response)
	{
		global $APPLICATION;
		$APPLICATION->restartBuffer();

		echo $response;

		$this->end();
	}

	/**
	 * Getting array of errors.
	 * @return Error[]
	 */
	public function getErrors()
	{
		return $this->errorCollection->toArray();
	}

	/**
	 * Getting once error with the necessary code.
	 * @param string $code Code of error.
	 * @return Error[]
	 */
	public function getErrorByCode($code)
	{
		return $this->errorCollection->getErrorByCode($code);
	}

	/**
	 * Resolves action and description of action, which need to run.
	 * @see listActions().
	 * @return $this
	 */
	protected function resolveAction()
	{
		$listOfActions = $this->normalizeListOfAction($this->listActions());
		$action = mb_strtolower($this->action);

		if(!isset($listOfActions[$action]))
		{
			$this->errorCollection->add(array(new Error(Loc::getMessage('REPORT_CONTROLLER_ERROR_UNKNOWN_ACTION',
				array('#ACTION#' => $action)), self::ERROR_UNKNOWN_ACTION)));
			return $this;
		}

		$this->realActionName = $action;
		$description = $listOfActions[$this->realActionName];
		$this->setAction($description['name'], $description);

		return $this;
	}

	//todo refactor BaseComponent + Controller normalizeListOfAction, resolveAction.
	//you can use composition in BaseComponent
	/**
	 * Normalizes list of action to general view.
	 * @param array $listOfActions List of action (@see listActions()).
	 * @return array
	 */
	protected function normalizeListOfAction(array $listOfActions)
	{
		$normalized = array();
		foreach($listOfActions as $action => $description)
		{
			if(!is_string($action))
			{
				$normalized[$description] = $this->normalizeActionDescription($description, $description);
			}
			else
			{
				$normalized[$action] = $this->normalizeActionDescription($action, $description);
			}
		}
		unset($action, $description);

		return array_change_key_case($normalized, CASE_LOWER);
	}

	/**
	 * Normalizes action description.
	 *
	 * Default description:
	 * array(
	 *			'method' => array('GET'), //allowed methods to run action.
	 *			'name' => $action, //action which will run
	 *			'check_csrf_token' => false, // check csrf token
	 *			'redirect_on_auth' => true, // if user doesn't authorize then will redirect on login page.
	 *			'close_session' => false, //execute session_close() before action.
	 *	)
	 *
	 * @param string $action Action name.
	 * @param array|string $description Action description.
	 * @return array
	 */
	protected function normalizeActionDescription($action, $description)
	{
		if(!is_array($description))
		{
			$description = array(
				'method' => array('GET'),
				'name' => $description,
				'check_csrf_token' => false,
				'redirect_on_auth' => true,
				'close_session' => false,
			);
		}
		if(empty($description['name']))
		{
			$description['name'] = $action;
		}
		if(!isset($description['redirect_on_auth']))
		{
			$description['redirect_on_auth'] = false;
		}
		if(!isset($description['close_session']))
		{
			$description['close_session'] = false;
		}

		return $description;
	}

	/**
	 * Checks action by settings in description.
	 * This method may terminate controller and application.
	 * @return void
	 */
	protected function checkAction()
	{
		if($this->errorCollection->count())
		{
			$this->sendJsonErrorResponse();
		}
		$description = $this->getActionDescription();

		if(!$this->getUser() || !$this->getUser()->getId())
		{
			if($description['redirect_on_auth'])
			{
				LocalRedirect(SITE_DIR . 'auth/?backurl=' .
					urlencode(Application::getInstance()->getContext()->getRequest()->getRequestUri()));
			}
			else
			{
				$this->runProcessingIfUserNotAuthorized();
			}
		}

		//if does not exist check_csrf_token we have to check csrf for only POST method.
		if(
			(isset($description['check_csrf_token']) && $description['check_csrf_token'] === true) ||
			($this->request->isPost() && !isset($description['check_csrf_token'])))
		{
			//in BDisk we have token_sid
			if(!check_bitrix_sessid() && !check_bitrix_sessid('token_sid'))
			{
				$this->runProcessingIfInvalidCsrfToken();
			}
		}

		if(!in_array($this->request->getRequestMethod(), $description['method']))
		{
			$this->sendJsonAccessDeniedResponse('Wrong method for current action');
		}
	}

	/**
	 * Lists all actions by controller. This listing may contains description in short-style.
	 *
	 * If you set
	 * array(
	 *  'showTest'
	 * )
	 *
	 * Then action 'showTest' has default description @see normalizeActionDescription()
	 * and execute method processActionShowTest.
	 *
	 * If you set
	 * array(
	 *  'showFoo' => 'showBar'
	 * )
	 *
	 * Then action 'showFoo' has default description @see normalizeActionDescription()
	 * and execute method processActionShowBar.
	 *
	 * If you set
	 * array(
	 *  'showFoo' => array(
	 *		'method' => array('GET', 'POST'), //allowed GET and POST methods to run action.
	 *		'name' => 'showFoo', //execute method processActionShowFoo
	 *		'check_csrf_token' => true, // check csrf token and run @see runProcessingIfInvalidCsrfToken()
	 *		'redirect_on_auth' => true, // if user doesn't authorize then will redirect on login page.
	 *		'close_session' => false, //execute session_close() before action.
	 *  )
	 * )
	 *
	 * @return array
	 */
	protected function listActions()
	{
		return array();
	}

	/**
	 * Gets current action.
	 * @return string
	 */
	public function getAction()
	{
		return $this->action;
	}

	/**
	 * Gets description of action.
	 * @return array
	 */
	public function getActionDescription()
	{
		return $this->actionDescription;
	}

	/**
	 * Sets action and description.
	 * @param string $action Action name.
	 * @param array  $description Action description.
	 * @return $this
	 */
	public function setAction($action, array $description)
	{
		$this->action = $action;
		$this->actionDescription = $description;

		return $this;
	}

	/**
	 * Sets action name.
	 * @param string $action Action name.
	 * @return $this
	 */
	public function setActionName($action)
	{
		$this->action = $action;
		return $this;
	}

	/**
	 * Checks required modules before process action.
	 * @return void
	 */
	protected function checkRequiredModules()
	{}

	/**
	 * Prepare params before process action.
	 * @return bool
	 */
	protected function prepareParams()
	{
		return true;
	}

	/**
	 * Common operations before process action.
	 * @param string $actionName Action name which will be run.
	 * @return bool If method will return false, then action will not execute.
	 */
	protected function processBeforeAction($actionName)
	{
		return true;
	}

	protected function runAction()
	{
		$description = $this->getActionDescription();
		if($description['close_session'] === true)
		{
			//todo be careful by using this features.
			session_write_close();
		}
		$actionMethod = 'processAction' . $this->getAction();

		return $this->$actionMethod();
	}

	/**
	 * Runs processing exception.
	 * @param \Exception $e Exception.
	 * @return void
	 */
	protected function runProcessingException(\Exception $e)
	{
//		throw $e;
		$this->errorCollection->add(array(new Error($e->getMessage())));
		$this->sendJsonErrorResponse();
	}

	/**
	 * Runs processing if user is not authorized.
	 * @return void
	 */
	protected function runProcessingIfUserNotAuthorized()
	{
		$this->sendJsonAccessDeniedResponse();
	}

	/**
	 * Runs processing if csrf token is invalid.
	 * @return void
	 */
	protected function runProcessingIfInvalidCsrfToken()
	{
		$this->sendJsonAccessDeniedResponse('Wrong csrf token');
	}

	/**
	 * Get application instance.
	 * @return Application|\Bitrix\Main\HttpApplication|\CAllMain|\CMain
	 */
	protected function getApplication()
	{
		global $APPLICATION;
		return $APPLICATION;
	}

	/**
	 * Checks required parameters.
	 * Fills error collection if required parameter is missed.
	 * @param array $inputParams Input data.
	 * @param array $required Required parameters.
	 * @return bool
	 */
	protected function checkRequiredInputParams(array $inputParams, array $required)
	{
		foreach ($required as $item)
		{
			if(!isset($inputParams[$item]) || (!$inputParams[$item] &&
				!(is_string($inputParams[$item]) && mb_strlen($inputParams[$item]))))
			{
				$this->errorCollection->add(array(new Error(
					Loc::getMessage('REPORT_CONTROLLER_ERROR_REQUIRED_PARAMETER',
						array('#PARAM#' => $item)), self::ERROR_REQUIRED_PARAMETER)));
				return false;
			}
		}

		return true;
	}

	/**
	 * Checks required parameters in $_POST.
	 * Fills error collection if required parameter is missed.
	 * @param array $required Required parameters.
	 * @return bool
	 */
	protected function checkRequiredPostParams(array $required)
	{
		$params = array();
		foreach($required as $item)
		{
			$params[$item] = $this->request->getPost($item);
		}
		unset($item);

		return $this->checkRequiredInputParams($params, $required);
	}

	/**
	 * Checks required parameters in $_GET.
	 * Fills error collection if required parameter is missed.
	 * @param array $required Required parameters.
	 * @return bool
	 */
	protected function checkRequiredGetParams(array $required)
	{
		$params = array();
		foreach($required as $item)
		{
			$params[$item] = $this->request->getQuery($item);
		}
		unset($item);

		return $this->checkRequiredInputParams($params, $required);
	}

	/**
	 * Checks required parameters in $_FILES.
	 * Fills error collection if required parameter is missed.
	 * @param array $required Required parameters.
	 * @return bool
	 */
	protected function checkRequiredFilesParams(array $required)
	{
		$params = array();
		foreach($required as $item)
		{
			$params[$item] = $this->request->getFile($item);
		}
		unset($item);

		return $this->checkRequiredInputParams($params, $required);
	}

	/**
	 * Returns whether this is an AJAX (XMLHttpRequest) request.
	 * @return boolean
	 */
	protected function isAjaxRequest()
	{
		return $this->request->isAjaxRequest();
	}
}