<?php
if (!defined("ROOT_PATH"))
{
	header("HTTP/1.1 403 Forbidden");
	exit;
}
if (is_file(PJ_FRAMEWORK_PATH . 'pjController.class.php'))
{
	require_once PJ_FRAMEWORK_PATH . 'pjController.class.php';
}
class pjAppController extends pjController
{
	public $models = array();

	/**
	 * Generate (or return existing) CSRF token for the current session.
	 */
	public static function getCsrfToken()
	{
		if (empty($_SESSION['csrf_token']))
		{
			$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
		}
		return $_SESSION['csrf_token'];
	}

	/**
	 * Validate CSRF token (admin requests).
	 */
	public static function validateCsrfToken()
	{
		$token = '';
		if (!empty($_POST['csrf_token'])) {
			$token = $_POST['csrf_token'];
		} elseif (!empty($_SERVER['HTTP_X_CSRF_TOKEN'])) {
			$token = $_SERVER['HTTP_X_CSRF_TOKEN'];
		}
		if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], (string) $token))
		{
			header('HTTP/1.1 403 Forbidden');
			exit('Invalid CSRF token.');
		}
	}
	
	public $option_arr = array();

	public $defaultLocale = 'admin_locale_id';
	
	public $defaultFields = 'fields';
	
	public $defaultFieldsIndex = 'fields_index';
	
	protected function loadSetFields($force=FALSE, $locale_id=NULL, $fields=NULL)
	{
		if (is_null($locale_id))
		{
			$locale_id = $this->getLocaleId();
		}
		
		if (is_null($fields))
		{
			$fields = $this->defaultFields;
		}
		
		$registry = pjRegistry::getInstance();
		if ($force
				|| !isset($_SESSION[$this->defaultFieldsIndex])
				|| $_SESSION[$this->defaultFieldsIndex] != $this->option_arr['o_fields_index']
				|| !isset($_SESSION[$fields])
				|| empty($_SESSION[$fields]))
		{
			pjAppController::setFields($locale_id);
	
			# Update session
			if ($registry->is('fields'))
			{
				$_SESSION[$fields] = $registry->get('fields');
			}
			$_SESSION[$this->defaultFieldsIndex] = $this->option_arr['o_fields_index'];
		}
	
		if (isset($_SESSION[$fields]) && !empty($_SESSION[$fields]))
		{
			# Load fields from session
			$registry->set('fields', $_SESSION[$fields]);
		}
		
		return TRUE;
	}
	
	public static function setTimezone($timezone="UTC")
    {
    	if (in_array(version_compare(phpversion(), '5.1.0'), array(0,1)))
		{
			date_default_timezone_set($timezone);
		} else {
			$safe_mode = ini_get('safe_mode');
			if ($safe_mode)
			{
				putenv("TZ=".$timezone);
			}
		}
    }

	public static function setMySQLServerTime($offset="-0:00")
    {
		pjAppModel::factory()->prepare("SET SESSION time_zone = :offset;")->exec(array('offset' => $offset));
    }
    
	public function setTime()
	{
		if (isset($this->option_arr['o_timezone']))
		{
			$offset = $this->option_arr['o_timezone'] / 3600;
			if ($offset > 0)
			{
				$offset = "-".$offset;
			} elseif ($offset < 0) {
				$offset = "+".abs($offset);
			} elseif ($offset === 0) {
				$offset = "+0";
			}
	
			pjAppController::setTimezone('Etc/GMT' . $offset);
			if (strpos($offset, '-') !== false)
			{
				$offset = str_replace('-', '+', $offset);
			} elseif (strpos($offset, '+') !== false) {
				$offset = str_replace('+', '-', $offset);
			}
			pjAppController::setMySQLServerTime($offset . ":00");
		}
	}
    
    public function beforeFilter()
    {
		if (strtoupper(@$_SERVER['REQUEST_METHOD']) === 'POST')
		{
			$_csrf_ctrl = isset($_GET['controller']) ? $_GET['controller'] : '';
			$_csrf_act = isset($_GET['action']) ? $_GET['action'] : '';
			if (strpos($_csrf_ctrl, 'pjAdmin') === 0
				&& !in_array($_csrf_act, array('pjActionLogin', 'pjActionForgot', 'pjActionLogout'), true)) {
				pjAppController::validateCsrfToken();
			}
		}

        $this->appendJs('jquery.min.js', PJ_THIRD_PARTY_PATH . 'jquery/');
        $baseDir = defined("PJ_INSTALL_PATH") ? PJ_INSTALL_PATH : null;
        $dm = new pjDependencyManager($baseDir, PJ_THIRD_PARTY_PATH);
        $dm->load(PJ_CONFIG_PATH . 'dependencies.php')->resolve();
		$this->appendJs('jquery-migrate.min.js', $dm->getPath('jquery_migrate'), FALSE, FALSE);
		$this->appendJs('pjAdminCore.js');
		$this->appendCss('reset.css');
		 
		$this->appendJs('js/jquery-ui.custom.min.js', PJ_THIRD_PARTY_PATH . 'jquery_ui/');
		$this->appendCss('css/smoothness/jquery-ui.min.css', PJ_THIRD_PARTY_PATH . 'jquery_ui/');
				
		$this->appendCss('pj-all.css', PJ_FRAMEWORK_LIBS_PATH . 'pj/css/');
		$this->appendCss('admin.css');
		
    	if ($_GET['controller'] != 'pjInstaller')
		{
			$this->models['Option'] = pjOptionModel::factory();
			$this->option_arr = $this->models['Option']->getPairs($this->getForeignId());
			$this->set('option_arr', $this->option_arr);
			$this->setTime();
			
			if (!isset($_SESSION[$this->defaultLocale]))
			{
				$locale_arr = pjLocaleModel::factory()->where('is_default', 1)->limit(1)->findAll()->getData();
				if (count($locale_arr) === 1)
				{
					$this->setLocaleId($locale_arr[0]['id']);
				}
			}
			$this->loadSetFields();
		}
    }
    
    public function getForeignId()
    {
    	return 1;
    }
    
	public function isInvoiceReady()
	{
		return $this->isAdmin();
	}
	
	public function isCountryReady()
	{
		return $this->isAdmin();
	}
	public function isOneAdminReady()
	{
		return $this->isAdmin();
	}
	
    public static function setFields($locale)
    {
   		if(isset($_SESSION['lang_show_id']) && (int) $_SESSION['lang_show_id'] == 1)
		{
			$fields = pjMultiLangModel::factory()
				->select('CONCAT(t1.content, CONCAT(":", t2.id, ":")) AS content, t2.key')
				->join('pjField', "t2.id=t1.foreign_id", 'inner')
				->where('t1.locale', $locale)
				->where('t1.model', 'pjField')
				->where('t1.field', 'title')
				->findAll()
				->getDataPair('key', 'content');
		}else{
			$fields = pjMultiLangModel::factory()
				->select('t1.content, t2.key')
				->join('pjField', "t2.id=t1.foreign_id", 'inner')
				->where('t1.locale', $locale)
				->where('t1.model', 'pjField')
				->where('t1.field', 'title')
				->findAll()
				->getDataPair('key', 'content');
		}
		$registry = pjRegistry::getInstance();
		$tmp = array();
		if ($registry->is('fields'))
		{
			$tmp = $registry->get('fields');
		}
		$arrays = array();
		foreach ($fields as $key => $value)
		{
			if (strpos($key, '_ARRAY_') !== false)
			{
				list($prefix, $suffix) = explode("_ARRAY_", $key);
				if (!isset($arrays[$prefix]))
				{
					$arrays[$prefix] = array();
				}
				$arrays[$prefix][$suffix] = $value;
			}
		}
		require PJ_CONFIG_PATH . 'settings.inc.php';
		$fields = array_merge($tmp, $fields, $settings, $arrays);
		$registry->set('fields', $fields);
    }

    public static function jsonDecode($str)
	{
		$Services_JSON = new pjServices_JSON();
		return $Services_JSON->decode($str);
	}
	
	public static function jsonEncode($arr)
	{
		$Services_JSON = new pjServices_JSON();
		return $Services_JSON->encode($arr);
	}
	
	public static function jsonResponse($arr)
	{
		header("Content-Type: application/json; charset=utf-8");
		echo pjAppController::jsonEncode($arr);
		exit;
	}

	public function getLocaleId()
	{
		return isset($_SESSION[$this->defaultLocale]) && (int) $_SESSION[$this->defaultLocale] > 0 ? (int) $_SESSION[$this->defaultLocale] : false;
	}
	public function getDirection()
	{
		$dir = 'ltr';
		if($this->getLocaleId() != false)
		{
			$locale_arr = pjLocaleModel::factory()->find($this->getLocaleId())->getData();
			$dir = $locale_arr['dir'];
		}
		return $dir;
	}
	public function setLocaleId($locale_id)
	{
		$_SESSION[$this->defaultLocale] = (int) $locale_id;
	}
	
	public static function friendlyURL($str, $divider='-')
	{
		$str = mb_strtolower($str, mb_detect_encoding($str));
		$str = trim($str);
		$str = preg_replace('/[_|\s]+/', $divider, $str);
		$str = preg_replace('/\x{00C5}/u', 'AA', $str);
		$str = preg_replace('/\x{00C6}/u', 'AE', $str);
		$str = preg_replace('/\x{00D8}/u', 'OE', $str);
		$str = preg_replace('/\x{00E5}/u', 'aa', $str);
		$str = preg_replace('/\x{00E6}/u', 'ae', $str);
		$str = preg_replace('/\x{00F8}/u', 'oe', $str);
		$str = preg_replace('/[^a-z\x{0400}-\x{04FF}0-9-]+/u', '', $str);
		$str = preg_replace('/[-]+/', $divider, $str);
		$str = preg_replace('/^-+|-+$/', '', $str);
		return $str;
	}

	public function getPrice($foreign_id, $type, $dt_from, $dt_to)
	{
		list($date_from, $time_from) = explode(" ", $dt_from);
		list($date_to, $time_to) = explode(" ", $dt_to);
		list($hour_from, $minute_from,) = explode(":", $time_from);
		list($hour_to, $minute_to,) = explode(":", $time_to);
		list($year_from, $month_from, $day_from) = explode("-", $date_from);
		list($year_to, $month_to, $day_to) = explode("-", $date_to);
		
		$seconds = abs(mktime((int) $hour_from, (int) $minute_from, 0, (int) $month_from, (int) $day_from, (int) $year_from) - mktime((int) $hour_to, (int) $minute_to, 0, (int) $month_to, (int) $day_to, (int) $year_to));
		$only_hours = ceil($seconds / 3600);
		$only_days = ceil($seconds / 86400);
		$time = pjUtil::secondsToTime($seconds);
		
		$pjPriceModel = pjPriceModel::factory();
		switch ($type)
		{
			case 'item':
				$data = pjItemModel::factory()->find($foreign_id)->getData();
				
				$pjPriceModel->join('pjItem', 't2.id=t1.foreign_id', 'inner')
					->where('t1.type', 'item');
				break;
			case 'package':
				$data = pjPackageModel::factory()->find($foreign_id)->getData();
				
				$pjPriceModel->join('pjPackage', 't2.id=t1.foreign_id', 'inner')
					->where('t1.type', 'package');
				break;
		}
		
		if (!isset($data) || empty($data))
		{
			return false;
		}
		
		$price_per_hour = $data['price_per_hour'];
		$price_per_day = $data['price_per_day'];
		
		$arr = $pjPriceModel
			->where('t1.foreign_id', $foreign_id)
			->where('t1.type', $type)
			->where('t1.date_from <=', $date_to)
			->where('t1.date_to >=', $date_from)
			->findAll()
			->getData();
		
		$rent_by_hour = (int) $data['rent_by_hour'] === 1;
		$rent_by_day = (int) $data['rent_by_day'] === 1;

		$hours = $time['h'];
		$days = $time['d'];
		if ($rent_by_hour && $rent_by_day)
		{
			$hours = $time['h'];
			if($hours > 0)
			{
				$days = $time['d'] - 1; 
			}else{
				$days = $time['d'];
			}
		} elseif ($rent_by_hour && !$rent_by_day) {
			$hours = $only_hours;
			$days = 0;
		} elseif (!$rent_by_hour && $rent_by_day) {
			$hours = 0;
			$days = $time['d'];
		}
		foreach ($arr as $price)
		{
			if ($price['period'] == 'hour' && $price['length_from'] <= $hours && $price['length_to'] >= $hours)
			{
				$price_per_hour = $price['price'];
			} elseif ($price['period'] == 'day' && $price['length_from'] <= $days && $price['length_to'] >= $days) {
				$price_per_day = $price['price'];
			}
		}
		
		$price = 0;
		if ($rent_by_hour && $rent_by_day)
		{
			$price += $days * $price_per_day;
			$price += $hours * $price_per_hour;
		} elseif ($rent_by_hour && !$rent_by_day) {
			$price += $hours * $price_per_hour;
		} elseif (!$rent_by_hour && $rent_by_day) {
			$price += $days * $price_per_day;
		}
			
		return $price;
	}
	
	public function pjActionAfterInstall()
	{
		$pjAppModel = pjAppModel::factory();
				
		$stmt1 = sprintf("DROP VIEW IF EXISTS `%1\$s%2\$spackages_items_availability`;", PJ_PREFIX, PJ_SCRIPT_PREFIX);
		$stmt2 = sprintf("CREATE SQL SECURITY INVOKER VIEW `%1\$s%2\$spackages_items_availability` AS
			SELECT pi.package_id, pi.item_id, a.dt
			FROM `%1\$s%2\$spackages_items` AS `pi`
			INNER JOIN `%1\$s%2\$spackages` AS `p` ON p.id = pi.package_id
			INNER JOIN `%1\$s%2\$sitems` AS `i` ON i.id = pi.item_id AND i.is_active = 1
			LEFT JOIN `%1\$s%2\$savailability` AS `a` ON a.item_id = pi.item_id
			ORDER BY pi.package_id ASC, pi.item_id ASC, a.dt ASC;", PJ_PREFIX, PJ_SCRIPT_PREFIX);
		
		$stmt3 = sprintf("DROP VIEW IF EXISTS `%1\$s%2\$spackages_availability`;", PJ_PREFIX, PJ_SCRIPT_PREFIX);
		$stmt4 = sprintf("CREATE SQL SECURITY INVOKER VIEW `%1\$s%2\$spackages_availability` AS
			SELECT DISTINCT(pa.dt), pa.package_id,
			(SELECT COUNT(*)
			    FROM `%1\$s%2\$spackages_items_availability`
			    WHERE `package_id` = pa.package_id
			    AND `dt` = pa.dt) AS `cnt`
			FROM `%1\$s%2\$spackages_items_availability` AS `pa`
			WHERE 1
			HAVING `cnt` > 0
			ORDER BY pa.package_id ASC, pa.dt ASC;", PJ_PREFIX, PJ_SCRIPT_PREFIX);
		
		$pjAppModel->prepare($stmt1)->exec()->prepare($stmt2)->exec()->prepare($stmt3)->exec()->prepare($stmt4)->exec();
		
		pjInvoiceConfigModel::factory()->set('id', 1)->modify(array(
			'o_booking_url' => "index.php?controller=pjAdminBookings&action=pjActionUpdate&uuid={BOOKING_ID}"
		));
		
		$query = sprintf("UPDATE `%s`
			SET `content` = :content
			WHERE `model` = :model
			AND `foreign_id` = (SELECT `id` FROM `%s` WHERE `key` = :key LIMIT 1)
			AND `field` = :field",
			pjMultiLangModel::factory()->getTable(), pjFieldModel::factory()->getTable()
		);
		$pjAppModel->reset()->prepare($query)->exec(array(
			'content' => 'Booking URL - Token: {BOOKING_ID}',
			'model' => 'pjField',
			'field' => 'title',
			'key' => 'plugin_invoice_i_booking_url'
		));
		
		$query = sprintf("UPDATE `%s`
			SET `label` = :label
			WHERE `key` = :key
			LIMIT 1",
			pjFieldModel::factory()->getTable()
		);
		$pjAppModel->reset()->prepare($query)->exec(array(
			'label' => 'Invoice plugin / Booking URL - Token: {BOOKING_ID}',
			'key' => 'plugin_invoice_i_booking_url'
		));
		
		pjCalendarModel::factory()->init();
		
		pjLocaleModel::factory()->where("`id` != '1'")->eraseAll();
		pjMultiLangModel::factory()->where("`locale` != '1'")->eraseAll();
		
		
		pjOptionModel::factory()->whereIn('`key`', array('o_paypal_client_id','o_paypal_client_secret','o_paypal_cancel_url'))->modifyAll(array('tab_id' => 7, 'order' => 17));
		
		return array('code' => 200, 'info' => array());
	}
	
	protected function pjActionGenerateInvoice($booking_id)
	{
		if (!isset($booking_id) || (int) $booking_id <= 0)
		{
			return array('status' => 'ERR', 'code' => 400, 'text' => 'ID is not set ot invalid.');
		}
		$arr = pjBookingModel::factory()->find($booking_id)->getData();
		if (empty($arr))
		{
			return array('status' => 'ERR', 'code' => 404, 'text' => 'Order not found.');
		}
		
		$bi_arr = pjBookingItemModel::factory()
			->select("t1.*, t2.content AS title")
			->join('pjMultiLang', sprintf("t2.model=IF(t1.type='item','pjItem','pjPackage') AND t2.foreign_id=t1.foreign_id AND t2.field='title' AND t2.locale='%u'", $arr['locale_id']), 'left outer')
			->where('t1.booking_id', $booking_id)
			->findAll()
			->getData();
		
		$items = array();
		if (!empty($bi_arr))
		{
			foreach ($bi_arr as $item)
			{
				$items[] = array(
					'name' => $item['title'],
					'description' => NULL,
					'qty' => $item['qty'],
					'unit_price' => $item['unit_price'],
					'amount' => number_format($item['qty'] * $item['unit_price'], 2, ".", "")
				);
			}
			$items[] = array(
				'name' => __('booking_security', true),
				'description' => NULL,
				'qty' => 1,
				'unit_price' => $arr['security'],
				'amount' => $arr['security']
			);
			$backs = __('booking_backs', true);
			$forths = __('booking_forths', true);
			$items[] = array(
				'name' => pjSanitize::html(@$forths['delivery']),
				'description' => NULL,
				'qty' => 1,
				'unit_price' => $arr['delivery'],
				'amount' => $arr['delivery']
			);
			$items[] = array(
				'name' => pjSanitize::html(@$backs['collection']),
				'description' => NULL,
				'qty' => 1,
				'unit_price' => $arr['collection'],
				'amount' => $arr['collection']
			);
		} else {
			$items[] = array(
				'name' => 'Booking payment',
				'description' => '',
				'qty' => 1,
				'unit_price' => $arr['total'],
				'amount' => $arr['total']
			);
		}
		
		$map = array(
			'confirmed' => 'paid',
			'cancelled' => 'cancelled',
			'pending' => 'not_paid'
		);
		
		$subtotal = $arr['deposit'] - ((float) $this->option_arr['o_tax'] * $arr['tax']) / 100;
		$tax = 0;
		if ((float) $this->option_arr['o_tax'] > 0 && $subtotal > 0)
		{
			$tax = ($subtotal * (float) $this->option_arr['o_tax']) / 100;
		}
		
		$response = $this->requestAction(
			array(
	    		'controller' => 'pjInvoice',
	    		'action' => 'pjActionCreate',
	    		'params' => array(
    				'key' => md5($this->option_arr['private_key'] . PJ_SALT),
				
					'uuid' => pjUtil::uuid(),
					'order_id' => $arr['uuid'],
					'foreign_id' => $arr['calendar_id'],
					'issue_date' => ':CURDATE()',
					'due_date' => ':CURDATE()',
					'created' => ':NOW()',
				
					'status' => @$map[$arr['status']],
					//'subtotal' => $arr['price'] + $arr['security'],
					'subtotal' => $arr['price'] + $arr['delivery'] + $arr['collection'],
				
					'tax' => $arr['tax'],
					//'tax' => $tax,
				
					'total' => $arr['total'],
					'paid_deposit' => $arr['deposit'],
					'amount_due' => $arr['total'] - ($arr['deposit'] - $arr['security']),
					'currency' => $this->option_arr['o_currency'],
					'notes' => $arr['c_notes'],
					'b_billing_address' => $arr['c_address_1'],
					'b_name' => $arr['c_name'],
					'b_address' => $arr['c_address_1'],
					'b_street_address' => $arr['c_address_2'],
					'b_city' => $arr['c_city'],
					'b_state' => $arr['c_state'],
					'b_zip' => $arr['c_zip'],
					'b_phone' => $arr['c_phone'],
					'b_email' => $arr['c_email'],
					'items' => $items
					)
    		),
    		array('return')
		);

		return $response;
	}
	
	public static function getTokens($booking_arr, $option_arr)
    {
    	$bi_arr = pjBookingItemModel::factory()
	    	->select("t1.*, t2.content AS title")
	    	->join('pjMultiLang', sprintf("t2.model=IF(t1.type='item','pjItem','pjPackage') AND t2.foreign_id=t1.foreign_id AND t2.field='title' AND t2.locale='%u'", $booking_arr['locale_id']), 'left outer')
	    	->where('t1.booking_id', $booking_arr['id'])
	    	->findAll()
	    	->getData();
    	
    	$items = '';
    	$item_arr = array();
    	if (!empty($bi_arr))
    	{
    		foreach ($bi_arr as $item)
    		{
    			$item_arr[] = $item['title'] . ' ('.pjUtil::formatCurrencySign($item['unit_price'], $option_arr['o_currency']).') x ' . $item['qty'] . ' = ' . pjUtil::formatCurrencySign(number_format($item['unit_price'] * $item['qty'], 2), $option_arr['o_currency']) ;
    		}
    	}
    	$items = implode("<br/>", $item_arr);
    	
    	$dt_from = date($option_arr['o_date_format'], strtotime($booking_arr['dt_from'])) . ', ' . date($option_arr['o_time_format'], strtotime($booking_arr['dt_from']));
    	$dt_to = date($option_arr['o_date_format'], strtotime($booking_arr['dt_to'])) . ', ' . date($option_arr['o_time_format'], strtotime($booking_arr['dt_to']));
    	
    	$booking_forths = __('booking_forths', true);
    	$booking_backs = __('booking_backs', true);
    	$payment_methods = __('payment_methods', true);
    	
    	$search = array(
    		'{Name}', '{Email}', '{Phone}', '{Notes}', '{Address1}',
    		'{Address2}', '{City}', '{Country}', '{State}', '{Back}', '{Forth}',
    		'{Zip}', '{CCType}', '{CCNum}', '{CCExpMonth}', '{CCExpYear}',
    		'{CCSec}', '{PaymentMethod}', '{DtFrom}', '{DtTo}', '{Items}', '{Deposit}',
    		'{Security}', '{Tax}', '{Total}', '{BookingID}',
    		'{BookingUUID}', '{CancelURL}');
		$replace = array(
			pjSanitize::clean($booking_arr['c_name']), pjSanitize::clean($booking_arr['c_email']), pjSanitize::clean($booking_arr['c_phone']), $booking_arr['c_notes'], pjSanitize::clean($booking_arr['c_address_1']),
		    pjSanitize::clean($booking_arr['c_address_2']), pjSanitize::clean($booking_arr['c_city']), @pjSanitize::clean($booking_arr['country']), pjSanitize::clean($booking_arr['c_state']), $booking_backs[$booking_arr['back']], $booking_forths[$booking_arr['forth']],
			$booking_arr['c_zip'], $booking_arr['cc_type'], $booking_arr['cc_num'], ($booking_arr['payment_method'] == 'creditcard' ? $booking_arr['cc_exp_month'] : NULL), ($booking_arr['payment_method'] == 'creditcard' ? $booking_arr['cc_exp_year'] : NULL),
		    $booking_arr['cc_code'], $payment_methods[$booking_arr['payment_method']], $dt_from, $dt_to, $items, $booking_arr['deposit'] . " " . $option_arr['o_currency'],
			$booking_arr['security'] . " " . $option_arr['o_currency'], $booking_arr['tax'] . " " . $option_arr['o_currency'], $booking_arr['total'] . " " . $option_arr['o_currency'], $booking_arr['id'],
			$booking_arr['uuid'], sprintf("%sindex.php?controller=pjFrontEnd&action=pjActionCancel&id=%u&hash=%s", PJ_INSTALL_URL, $booking_arr['id'], sha1($booking_arr['id'] . PJ_SALT))
		);
		return compact('search', 'replace');
    }
}
?>