<?php
// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
// $Id: Utilities.php 66871 2018-07-03 23:24:40Z lindonb $

//this script may only be included - so its better to die if called directly.
if (strpos($_SERVER['SCRIPT_NAME'], basename(__FILE__)) !== false) {
	header('location: index.php');
	exit;
}

/**
 * Class Services_Utilities
 */
class Services_Utilities
{
	public $items;
	public $itemsCount;
	public $extra;
	public $toList;
	public $action;
	public $confirmController;

	/**
	 * Provide referer url if javascript not enabled.
	 *
	 * @return bool|string
	 */
	static function noJsPath()
	{
		global $prefs;
		//no javascript
		if ($prefs['javascript_enabled'] !== 'y') {
			global $base_url;
			$referer = substr($_SERVER['HTTP_REFERER'], strlen($base_url));
		//javascript
		} else {
			$referer = false;
		}
		return $referer;
	}

	/**
	 * Handle feedback after a non-modal form is clicked
	 * Send feedback using Feedback class (using 'session' for the method parameter) first before using this.
	 * Improves handling when javascript is not enabled compared to throwing a Services Exception because it takes the
	 * user back to the page where the action was initiated and shows the error message there.
	 *
	 * @param bool $referer
	 * @throws Exception
	 */
	static function sendFeedback($referer = false)
	{
		//no javascript
		if (! empty($referer)) {
			TikiLib::lib('access')->redirect($referer);
		//javascript
		} else {
			Feedback::send_headers();
			die;
		}
	}

	/**
	 * Handle Feedback message after a modal is clicked.
	 * Send feedback using Feedback class (using 'session' for the method parameter) first before using this.
	 * Improves handling when javascript is not enabled compared to throwing a Services Exception because it takes the
	 * user back to the page where the action was initiated and shows the error message there.
	 *
	 * @param bool $referer
	 * @return array
	 * @throws Exception
	 */
	static function closeModal($referer = false)
	{
		//no javascript
		if (! empty($referer)) {
			TikiLib::lib('access')->redirect($referer);
		//javascript
		} else {
			Feedback::send_headers();
			//the js confirmAction function in tiki-confirm.js uses this to close the modal
			return ['extra' => 'close'];
		}
	}

	/**
	 * Handle feedback message when the page is being refreshed, e.g., after a succesful action
	 * Send feedback using Feedback class (using 'session' for the method parameter) first before using this.
	 * Allows the same type of detailed feedback to be provided when javascript is not enabled.
	 *
	 * @param bool $referer
	 * @return array
	 * @throws Exception
	 */
	static function refresh($referer = false)
	{
		//no javascript
		if (! empty($referer)) {
			$referer = new JitFilter(['referer' => $referer]);
			TikiLib::lib('access')->redirect($referer->referer->striptags());
		//javascript
		} else {
			//the js confirmAction function in tiki-confirm.js uses this to close the modal and refresh the page
			return ['extra' => 'refresh'];
		}
	}

	/**
	 * Handle a redirect depending on whether javascript is enabled or not
	 * Send any feedback using Feedback class (using 'session' for the method parameter) first before using this.
	 *
	 * @param $url
	 * @return array
	 * @throws Exception
	 */
	static function redirect($url)
	{
		//no javascript
		global $prefs;
		if ($prefs['javascript_enabled'] !== 'y') {
			TikiLib::lib('access')->redirect($url);
		//javascript
		} else {
			return ['url' => $url];
		}
	}

	/**
	 * Handle exception when initially clicking a modal service action according to whether javascript is enabled or not.
	 * Improves handling when javascript is not enabled compared to throwing a Services Exception because it takes the
	 * user back to the page where the action was initiated and shows the error message there.
	 *
	 * @param $mes
	 * @throws Exception
	 * @throws Services_Exception
	 */
	static function modalException($mes)
	{
		$referer = self::noJsPath();
		//no javascript
		if (! empty($referer)) {
			Feedback::error($mes);
			TikiLib::lib('access')->redirect($referer);
		//javascript
		} else {
			//this will show as a modal if exception occurs when first clicking the action
			throw new Services_Exception($mes);
		}
	}

	/**
	 * The following functions are used in the services actions that first present a popup for confirmation before the
	 * action is completed by the user confirm the action
	 */


	/**
	 * CSRF ticket - Check the ticket to either set it or match to the ticket previously set
	 *
	 * @param string $error
	 * @return bool
	 * @throws Exception
	 * @throws Services_Exception
	 */
	function checkCsrf($error = 'services')
	{
		return TikiLib::lib('access')->checkCsrf($error);
	}

	function isConfirmPost()
	{
		$return = TikiLib::lib('access')->isActionPost() && isset($_POST['confirmForm']) && $_POST['confirmForm'] === 'y';
		if ($return) {
			return $this->checkCsrf('services');
		} else {
			return false;
		}
	}

	function notConfirmPost()
	{
		return ! TikiLib::lib('access')->isActionPost() || ! isset($_POST['confirmForm']) || $_POST['confirmForm'] !== 'y';
	}

	function isActionPost()
	{
		$access = TikiLib::lib('access');
		return $access->isActionPost() && $access->checkCsrf('services');
	}

	function setTicket()
	{
		return TikiLib::lib('access')->setTicket();
	}

	function getTicket()
	{
		return TikiLib::lib('access')->getTicket();
	}

	/**
	 * Set the items, action and extra variables, and apply any filters
	 *
	 * @param JitFilter $input
	 * @param array $filters
	 * @param bool $itemsOffset
	 * @throws Exception
	 */
	function setVars(JitFilter &$input, array $filters = [], $itemsOffset = false)
	{
		if (!empty($filters)) {
			$input->replaceFilters($filters);
		}
		$this->extra = $input->asArray();
		$this->action = $input->action->word();
		$this->confirmController = $input->controller->alnumdash();
		unset($this->extra['action'], $this->extra['controller'], $this->extra['modal']);
		if ($itemsOffset) {
			$this->items = $input->asArray($itemsOffset);
			$this->itemsCount = count($this->items);
			unset($this->extra[$itemsOffset]);
		}
	}


	function setDecodedVars(JitFilter &$input, array $filters = [])
	{
		//decode standard array values
		//no filters until after json decoding
		$offsets = ['items', 'extra', 'toList'];
		$tempinput = [];
		foreach ($offsets as $offset) {
			if ($input->offsetExists($offset)) {
				$tempinput[$offset] = json_decode($input->{$offset}->none(), true);
				$input->offsetUnset($offset);
			}
		}
		//convert into a JitFilter object
		$tempinput = new JitFilter($tempinput);
		$tempinput->setDefaultFilter('xss');
		//apply standard filters
		$tempinput->replaceFilters(['anchor' => 'striptags', 'referer' => 'striptags']);
		$input->replaceFilters(['anchor' => 'striptags', 'referer' => 'striptags']);
		//apply any filters specified in the method call
		if (!empty($filters)) {
			$tempinput->replaceFilters($filters);
			$input->replaceFilters($filters);
		}
		foreach ($offsets as $offset) {
			if ($tempinput->offsetExists($offset)) {
				$this->{$offset} = $tempinput[$offset]->asArray();
			}
			if ($offset === 'items' && isset($tempinput[$offset])) {
				$this->itemsCount = count($this->items);
			}
		}
	}

	/**
	 * Create array for standard confirmation popup
	 *
	 * @param $msg
	 * @param $button
	 * @param array $moreExtra
	 * @return array
	 */
	function confirm($msg, $button, array $moreExtra = [])
	{
		$thisExtra = [];
		if (is_array($this->extra)) {
			$thisExtra = $this->extra;
		} elseif ($this->extra instanceof JitFilter) {
			$thisExtra = $this->extra->asArray();
		} elseif (strlen($this->extra) > 0) {
			$thisExtra = [$this->extra];
		}
		//provide redirect if js is not enabled
		$extra['referer'] = ! empty($moreExtra['referer']) ? $moreExtra['referer'] : Services_Utilities::noJsPath();
		$extra = array_merge($thisExtra, $extra, $moreExtra);
		$ret = [
			'FORWARD' => [
				'modal' => '1',
				'controller' => 'access',
				'action' => 'confirm',
				'confirmAction' => $this->action,
				'confirmController' => $this->confirmController,
				'customMsg' => $msg,
				'confirmButton' => $button,
				'items' => $this->items,
				'extra' => $extra,
			]
		];
		return $ret;
	}
}
