<?php
// GIJOE's Ticket Class (based on Marijuana's Oreteki XOOPS)
// nobunobu's suggestions are applied
if (!class_exists('XoopsGTicket')) {

	class XoopsGTicket {
		var $_errors = array ();
		var $_latest_token = '';
		var $messages = array ();

		function __construct() {
			global $icmsConfig;

			// language file
			if (defined('ICMS_ROOT_PATH') && !empty($icmsConfig['language']) && !strstr($icmsConfig['language'], '/')) {
				if (file_exists(dirname(__DIR__) . '/language/' . $icmsConfig['language'] . '/gticket_messages.phtml')) {
					include dirname(__DIR__) . '/language/' . $icmsConfig['language'] . '/gticket_messages.phtml';
				}
			}

			// default messages
			if (empty($this->messages)) $this->messages = array (
				'err_general' => 'GTicket Error',
				'err_nostubs' => 'No stubs found',
				'err_noticket' => 'No ticket found',
				'err_nopair' => 'No valid ticket-stub pair found',
				'err_timeout' => 'Time out',
				'err_areaorref' => 'Invalid area or referer',
				'fmt_prompt4repost' => 'error(s) found:<br /><span style="background-color:red;font-weight:bold;color:white;">%s</span><br />Confirm it.<br />And do you want to post again?',
				'btn_repost' => 'repost',
			);
		}

		// render form as plain html
		function getTicketHtml($salt = '', $timeout = 1800, $area = '') {
			return '<input type="hidden" name="XOOPS_G_TICKET" value="' . $this->issue($salt, $timeout, $area) . '" />';
		}

		// returns an object of icms_form_elements_Hidden including theh ticket
		function getTicketXoopsForm($salt = '', $timeout = 1800, $area = '') {
			return new icms_form_elements_Hidden('XOOPS_G_TICKET', $this->issue($salt, $timeout, $area));
		}

		// add a ticket as Hidden Element into XoopsForm
		function addTicketicms_form_Element(&$form, $salt = '', $timeout = 1800, $area = '') {
			$form->addElement(new icms_form_elements_Hidden('XOOPS_G_TICKET', $this->issue($salt, $timeout, $area)));
		}

		// returns an array for xoops_confirm() ;
		function getTicketArray($salt = '', $timeout = 1800, $area = '') {
			return array (
				'XOOPS_G_TICKET' => $this->issue($salt, $timeout, $area)
			);
		}

		// return GET parameter string.
		function getTicketParamString($salt = '', $noamp = false, $timeout = 1800, $area = '') {
			return ($noamp ? '' : '&amp;') . 'XOOPS_G_TICKET=' . $this->issue($salt, $timeout, $area);
		}

		// issue a ticket
		function issue($salt = '', $timeout = 1800, $area = '') {
			global $xoopsModule;

			// create a token
			list($usec, $sec) = explode(" ", microtime());
			$appendix_salt = empty($_SERVER['PATH']) ? XOOPS_DB_NAME : $_SERVER['PATH'];
			$token = crypt($salt . $usec, $appendix_salt . $sec);
			$this->_latest_token = $token;

			if (empty($_SESSION['XOOPS_G_STUBS'])) $_SESSION['XOOPS_G_STUBS'] = array ();

			// limit max stubs 10
			if (sizeof($_SESSION['XOOPS_G_STUBS']) > 10) {
				$_SESSION['XOOPS_G_STUBS'] = array_slice($_SESSION['XOOPS_G_STUBS'], -10);
			}

			// record referer if browser send it
			$referer = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['REQUEST_URI'];

			// area as module's dirname
			if (!$area && is_object(@$xoopsModule)) {
				$area = $xoopsModule->getVar('dirname');
			}

			// store stub
			$_SESSION['XOOPS_G_STUBS'][] = array (
				'expire' => time() + $timeout,
				'referer' => $referer,
				'area' => $area,
				'token' => $token
			);

			// paid md5ed token as a ticket
			return md5($token . XOOPS_DB_PREFIX);
		}

		// check a ticket
		function check($post = true, $area = '', $allow_repost = true) {
			global $xoopsModule;

			$this->_errors = array ();

			// CHECK: stubs are not stored in session
			if (!is_array(@$_SESSION['XOOPS_G_STUBS'])) {
				$this->_errors[] = $this->messages['err_nostubs'];
				$_SESSION['XOOPS_G_STUBS'] = array ();
			}

			// get key&val of the ticket from a user's query
			$ticket = $post ? @$_POST['XOOPS_G_TICKET'] : @$_GET['XOOPS_G_TICKET'];

			// CHECK: no tickets found
			if (empty($ticket)) {
				$this->_errors[] = $this->messages['err_noticket'];
			}

			// gargage collection & find a right stub
			$stubs_tmp = $_SESSION['XOOPS_G_STUBS'];
			$_SESSION['XOOPS_G_STUBS'] = array ();
			foreach ($stubs_tmp as $stub) {
				// default lifetime 30min
				if ($stub['expire'] >= time()) {
					if (md5($stub['token'] . XOOPS_DB_PREFIX) === $ticket) {
						$found_stub = $stub;
					} else {
						// store the other valid stubs into session
						$_SESSION['XOOPS_G_STUBS'][] = $stub;
					}
				} else {
					if (md5($stub['token'] . XOOPS_DB_PREFIX) === $ticket) {
						// not CSRF but Time-Out
						$timeout_flag = true;
					}
				}
			}

			// CHECK: the right stub found or not
			if (empty($found_stub)) {
				if (empty($timeout_flag))
					$this->_errors[] = $this->messages['err_nopair'];
				else
					$this->_errors[] = $this->messages['err_timeout'];
			} else {

				// set area if necessary
				// area as module's dirname
				if (!$area && is_object(@$xoopsModule)) {
					$area = $xoopsModule->getVar('dirname');
				}

				// check area or referer
				if (@$found_stub['area'] == $area) $area_check = true;
				if (!empty($found_stub['referer']) && strstr(@$_SERVER['HTTP_REFERER'], $found_stub['referer'])) $referer_check = true;

				if (empty($area_check) && empty($referer_check)) { // loose
					$this->_errors[] = $this->messages['err_areaorref'];
				}
			}

			if (!empty($this->_errors)) {
				if ($allow_repost) {
					// repost form
					$this->draw_repost_form($area);
					exit();
				} else {
					// failed
					$this->clear();
					return false;
				}
			} else {
				// all green
				return true;
			}
		}

		// draw form for repost
		function draw_repost_form($area = '') {
			// Notify which file is broken
			if (headers_sent()) {
				restore_error_handler();
				set_error_handler(array (
					&$this,
					'errorHandler4FindOutput'
				));
				header('Dummy: for warning');
				restore_error_handler();
				exit();
			}

			error_reporting(0);
			while (ob_get_level())
				ob_end_clean();

			$table = '<table>';
			$form = '<form action="?' . htmlspecialchars(@$_SERVER['QUERY_STRING'], ENT_QUOTES) . '" method="post" >';
			foreach ($_POST as $key => $val) {
				if ($key == 'XOOPS_G_TICKET') continue;

				if (is_array($val)) {
					list($tmp_table, $tmp_form) = $this->extract_post_recursive(htmlspecialchars($key, ENT_QUOTES), $val);
					$table .= $tmp_table;
					$form .= $tmp_form;
				} else {

					$table .= '<tr><th>' . htmlspecialchars($key, ENT_QUOTES) . '</th><td>' . htmlspecialchars($val, ENT_QUOTES) . '</td></tr>' . "\n";
					$form .= '<input type="hidden" name="' . htmlspecialchars($key, ENT_QUOTES) . '" value="' . htmlspecialchars($val, ENT_QUOTES) . '" />' . "\n";
				}
			}
			$table .= '</table>';
			$form .= $this->getTicketHtml(__LINE__, 300, $area) . '<input type="submit" value="' . $this->messages['btn_repost'] . '" /></form>';

			echo '<html><head><title>' . $this->messages['err_general'] . '</title><style>table,td,th {border:solid black 1px; border-collapse:collapse;}</style></head><body>' . sprintf($this->messages['fmt_prompt4repost'], $this->getErrors()) . $table . $form . '</body></html>';
		}

		function extract_post_recursive($key_name, $tmp_array) {
			$table = '';
			$form = '';
			foreach ($tmp_array as $key => $val) {
				if (is_array($val)) {
					list($tmp_table, $tmp_form) = $this->extract_post_recursive($key_name . '[' . htmlspecialchars($key, ENT_QUOTES) . ']', $val);
					$table .= $tmp_table;
					$form .= $tmp_form;
				} else {
					$table .= '<tr><th>' . $key_name . '[' . htmlspecialchars($key, ENT_QUOTES) . ']</th><td>' . htmlspecialchars($val, ENT_QUOTES) . '</td></tr>' . "\n";
					$form .= '<input type="hidden" name="' . $key_name . '[' . htmlspecialchars($key, ENT_QUOTES) . ']" value="' . htmlspecialchars($val, ENT_QUOTES) . '" />' . "\n";
				}
			}
			return array (
				$table,
				$form
			);
		}

		// clear all stubs
		function clear() {
			$_SESSION['XOOPS_G_STUBS'] = array ();
		}

		// Ticket Using
		function using() {
			if (!empty($_SESSION['XOOPS_G_STUBS'])) {
				return true;
			} else {
				return false;
			}
		}

		// return errors
		function getErrors($ashtml = true) {
			if ($ashtml) {
				$ret = '';
				foreach ($this->_errors as $msg) {
					$ret .= "$msg<br />\n";
				}
			} else {
				$ret = $this->_errors;
			}
			return $ret;
		}

		function errorHandler4FindOutput($errNo, $errStr, $errFile, $errLine) {
			if (preg_match('?' . preg_quote(ICMS_ROOT_PATH) . '([^:]+)\:(\d+)?', $errStr, $regs)) {
				echo "Irregular output! check the file " . htmlspecialchars($regs[1]) . " line " . htmlspecialchars($regs[2]);
			} else {
				echo "Irregular output! check language files etc.";
			}
			return;
		}
		// end of class
	}

	// create a instance in global scope
	$GLOBALS['xoopsGTicket'] = new XoopsGTicket();
}

if (!function_exists('admin_refcheck')) {

	// Admin Referer Check By Marijuana(Rev.011)
	function admin_refcheck($chkref = "") {
		if (empty($_SERVER['HTTP_REFERER'])) {
			return true;
		} else {
			$ref = $_SERVER['HTTP_REFERER'];
		}
		$cr = ICMS_URL;
		if ($chkref != "") {
			$cr .= $chkref;
		}
		if (strpos($ref, $cr) !== 0) {
			return false;
		}
		return true;
	}
}
