<?php
/**
* @version	$Id: orders_tag_processor.php 16516 2017-01-20 14:12:22Z alex $
* @package	In-Commerce
* @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license	Commercial License
* This software is protected by copyright law and international treaties.
* Unauthorized reproduction or unlicensed usage of the code of this program,
* or any portion of it may result in severe civil and criminal penalties,
* and will be prosecuted to the maximum extent possible under the law
* See http://www.in-portal.org/commercial-license for copyright notices and details.
*/

	defined('FULL_PATH') or die('restricted access!');

	class OrdersTagProcessor extends kDBTagProcessor
	{

		/**
		 * Print location using only filled in fields
		 *
		 * @param Array $params
		 * @access public
		 */
		function PrintLocation($params)
		{
			$object = $this->getObject($params);

			$type = getArrayValue($params,'type');
			if($type == 'Company')
			{
				return $this->PrintCompanyLocation($params);
			}
			$fields = Array('City','State','Zip','Country');

			$ret = '';
			foreach($fields as $field)
			{
				$value = $object->GetField($type.$field);
				if ($field == 'Country' && $value) $ret .= '<br/>';
				if($value) $ret .= $value.', ';
			}
			return rtrim($ret,', ');
		}

		function PrintCompanyLocation($params)
		{
			$ret = '';
			$fields = Array ('City','State','ZIP','Country');

			foreach ($fields as $field) {
				$value = $this->Application->ConfigValue('Comm_'.$field);
				if ($field == 'Country') {
					$current_language = $this->Application->GetVar('m_lang');
					$primary_language = $this->Application->GetDefaultLanguageId();

					$sql = 'SELECT IF(l' . $current_language . '_Name = "", l' . $primary_language . '_Name, l' . $current_language . '_Name)
							FROM ' . TABLE_PREFIX . 'CountryStates
							WHERE IsoCode = ' . $this->Conn->qstr($value);
					$value = $this->Conn->GetOne($sql);
				}

				if ($field == 'Country' && $value) {
					$ret .= '<br/>';
				}

				if ($value) {
					$ret .= $value.', ';
				}
			}

			return rtrim($ret,', ');
		}

		function Orditems_LinkRemoveFromCart($params)
		{
			$params['m_cat_id'] = 0;
			$params['ord_event'] = 'OnRemoveFromCart';

			if ( !isset($params['pass']) ) {
				$params['pass'] = 'm,orditems,ord';
			}

			// Remove parameters, that indicate from where this aggregated tag was called.
			unset($params['PrefixSpecial'], $params['original_tag']);

			return $this->Application->ProcessParsedTag('m', 'Link', $params);
		}

		function Orderitems_ProductLink($params)
		{
			$object = $this->Application->recallObject('orditems');

			$url_params = Array (
				'p_id' =>  $object->GetDBField('ProductId'),
				'pass' => 'm,p',
			);

			return $this->Application->HREF($params['template'], '', $url_params);
		}

		function Orderitems_ProductExists($params)
		{
			$object = $this->Application->recallObject('orditems');
			return $object->GetDBField('ProductId') > 0;
		}

		function PrintCart($params)
		{
			$o = '';

			$params['render_as'] = $params['item_render_as'];
			$tag_params = array_merge($params, Array ('per_page' => -1));

			$o_items = $this->Application->ProcessParsedTag(rtrim('orditems.' . $this->Special, '.'), 'PrintList', $tag_params);

			if ( $o_items ) {
				if ( isset($params['header_render_as']) ) {
					$cart_params = array ('name' => $params['header_render_as']);
					$o .= $this->Application->ParseBlock($cart_params);
				}

				$o .= $o_items;

				if ( isset($params['footer_render_as']) ) {
					$cart_params = array ('name' => $params['footer_render_as']);
					$o .= $this->Application->ParseBlock($cart_params);
				}
			}
			elseif ( isset($params['empty_cart_render_as']) ) {
				$cart_params = array ('name' => $params['empty_cart_render_as']);
				$o = $this->Application->ParseBlock($cart_params);
			}

			return $o;
		}

		function ShopCartForm($params)
		{
			return $this->Application->ProcessParsedTag('m', 'ParseBlock', 	array_merge($params, Array(
					'name' => 'kernel_form', 'PrefixSpecial'=>'ord'
			)) );
		}

		function BackOrderFlag($params)
		{
			$object = $this->Application->recallObject('orditems');
			return $object->GetDBField('BackOrderFlag');
		}

		function OrderIcon($params)
		{
			$object = $this->Application->recallObject('orditems');
			if ($object->GetDBField('BackOrderFlag') == 0) {
				return $params['ordericon'];
			} else {
				return $params['backordericon'];
			}
		}

		function Status($params)
		{
			$status_map = Array(
				'incomplete' => ORDER_STATUS_INCOMPLETE,
				'pending' => ORDER_STATUS_PENDING,
				'backorder' => ORDER_STATUS_BACKORDERS,
				'toship' => ORDER_STATUS_TOSHIP,
				'processed' => ORDER_STATUS_PROCESSED,
				'denied' => ORDER_STATUS_DENIED,
				'archived' => ORDER_STATUS_ARCHIVED,
			);

			$object = $this->getObject($params);
			$status = $object->GetDBField('Status');

			$result = true;
			if (isset($params['is'])) {
				$result = $result && ($status == $status_map[$params['is']]);
			}
			if (isset($params['is_not'])) {
				$result = $result && ($status != $status_map[$params['is_not']]);
			}
			return $result;
		}

		function ItemsInCart($params)
		{
			/** @var kDBItem $object */
			$object = $this->getObject($params);

			if ( $object->GetDBField('Status') != ORDER_STATUS_INCOMPLETE || $object->GetID() == FAKE_ORDER_ID ) {
				return 0;
			}

			/** @var kDBList $object */
			$object = $this->Application->recallObject('orditems', 'orditems_List');

			$object->Query();

			return array_sum($object->GetCol('Quantity')); // $object->GetRecordsCount();
		}

		function CartNotEmpty($params)
		{
			$object = $this->getObject($params);

			if ($object->GetDBField('Status') != ORDER_STATUS_INCOMPLETE || $object->GetID() == FAKE_ORDER_ID) {
				return 0;
			}

			$order_id = $this->Application->RecallVar('ord_id');
			if ($order_id) {
				$sql = 'SELECT COUNT(*)
						FROM ' . TABLE_PREFIX . 'OrderItems
						WHERE OrderId = ' . $order_id;
				return $this->Conn->GetOne($sql);
			}

			return 0;
		}

		function CartIsEmpty($params)
		{
			return $this->CartNotEmpty($params) ? false : true;
		}

		function CartHasBackorders($params = Array ())
		{
			$object = $this->getObject($params);

			$sql = 'SELECT COUNT(*)
					FROM ' . TABLE_PREFIX . 'OrderItems
					WHERE OrderId = ' . $object->GetID() . '
					GROUP BY BackOrderFlag';
			$different_types = $this->Conn->GetCol($sql);

			return count($different_types) > 1;
		}

		function PrintShippings($params)
		{
			$o = '';
			$limitations_cache = Array ();

			/** @var kDBItem $object */
			$object = $this->getObject($params);

			$ord_id = $object->GetID();
			$oi_table = $this->Application->getUnitOption('orditems', 'TableName');

			if ( $object->IsTempTable() ) {
				$oi_table = $this->Application->GetTempName($oi_table, 'prefix:' . $object->Prefix);
			}

			list ($split_shipments, $limit_types) = $this->GetShippingLimitations($ord_id);

			foreach ($split_shipments as $group => $data) {
				$sql = 'UPDATE ' . $oi_table . '
						SET SplitShippingGroup = ' . $group . '
						WHERE ProductId IN (' . implode(',', $data['Products']) . ')';
				$this->Conn->Query($sql);

				$limitations_cache[$group] = $data['Types'];
			}


			$shipping_group_option = $object->GetDBField('ShippingGroupOption');
			$shipping_group_select = $shipping_group_option == ORDER_GROUP_SHIPPMENTS_AUTO ? '0' : 'oi.SplitShippingGroup';

			if ( count($split_shipments) > 1 ) {
				// different shipping limitations apply
				$this->Application->SetVar('shipping_limitations_apply', 1);

				if ( $limit_types == 'NONE' ) {
					// order can't be shipped with single shipping type
					$shipping_group_option = ORDER_GROUP_SHIPPMENTS_MANUAL;
					$shipping_group_select = 'oi.SplitShippingGroup';
					$this->Application->SetVar('shipping_limitations_apply', 2);
				}
			}
			else {
				$this->Application->SetVar('shipping_limitations_apply', 0);
			}




			$shipping_option = $object->GetDBField('ShippingOption');

			$weight_sql = 'IF(oi.Weight IS NULL, 0, oi.Weight * oi.Quantity)';

			$sql = 'SELECT
						' . ($shipping_option == ORDER_SHIP_ALL_TOGETHER ? '0' : 'oi.BackOrderFlag') . ' AS BackOrderFlagCalc,
						oi.ProductName,
						oi.ShippingTypeId,

						SUM(oi.Quantity) AS TotalItems,
						SUM(' . $weight_sql . ') AS TotalWeight,
						SUM(oi.Price * oi.Quantity) AS TotalAmount,

						SUM(oi.Quantity) - SUM(IF(p.MinQtyFreePromoShipping > 0 AND p.MinQtyFreePromoShipping <= oi.Quantity, oi.Quantity, 0)) AS TotalItemsPromo,
						SUM(' . $weight_sql . ') - SUM(IF(p.MinQtyFreePromoShipping > 0 AND p.MinQtyFreePromoShipping <= oi.Quantity, ' . $weight_sql . ', 0)) AS TotalWeightPromo,
						SUM(oi.Price * oi.Quantity) - SUM(IF(p.MinQtyFreePromoShipping > 0 AND p.MinQtyFreePromoShipping <= oi.Quantity, oi.Price * oi.Quantity, 0)) AS TotalAmountPromo,

						' . $shipping_group_select . ' AS SplitShippingGroupCalc
					FROM ' . $oi_table . ' oi
					LEFT JOIN ' . TABLE_PREFIX . 'Products p ON oi.ProductId = p.ProductId
					WHERE oi.OrderId = ' . $ord_id . ' AND p.Type = ' . PRODUCT_TYPE_TANGIBLE . '
					GROUP BY BackOrderFlagCalc, SplitShippingGroupCalc
					ORDER BY BackOrderFlagCalc ASC, SplitShippingGroupCalc ASC';
			$shipments = $this->Conn->Query($sql);






			$block_params = Array ();
			$block_params['name'] = $this->SelectParam($params, 'render_as,block');
			$block_params['user_country_id'] = $object->GetDBField('ShippingCountry');
			$block_params['user_state_id'] = $object->GetDBField('ShippingState');
			$block_params['user_zip'] = $object->GetDBField('ShippingZip');
			$block_params['user_city'] = $object->GetDBField('ShippingCity');
			$block_params['user_addr1'] = $object->GetDBField('ShippingAddress1');
			$block_params['user_addr2'] = $object->GetDBField('ShippingAddress2');
			$block_params['user_name'] = $object->GetDBField('ShippingTo');

			$group = 1;
			foreach ($shipments as $shipment) {
				$where = Array ('OrderId = ' . $ord_id);

				if ( $shipping_group_option == ORDER_GROUP_SHIPPMENTS_MANUAL ) {
					$where[] = 'SplitShippingGroup = ' . $shipment['SplitShippingGroupCalc'];
				}

				if ( $shipping_option != ORDER_SHIP_ALL_TOGETHER ) {
					$where[] = 'BackOrderFlag = ' . $shipment['BackOrderFlagCalc'];
				}

				$sql = 'UPDATE ' . $oi_table . '
						SET PackageNum = ' . $group . '
						WHERE ' . implode(' AND ', $where);
				$this->Conn->Query($sql);

				$group++;
			}


			$group = 1;
			$this->Application->RemoveVar('LastShippings');
			$this->Application->SetVar('ShipmentsExists', 1);

			foreach ($shipments as $shipment) {
				$block_params['package_num'] = $group;
				$block_params['limit_types'] = $shipping_group_option == ORDER_GROUP_SHIPPMENTS_AUTO ? $limit_types : $limitations_cache[ $shipment['SplitShippingGroupCalc'] ];

				$this->Application->SetVar('ItemShipmentsExists', 1); // also set from Order_PrintShippingTypes tag

				switch ( $shipment['BackOrderFlagCalc'] ) {
					case 0:
						if ( $this->CartHasBackOrders() && $shipping_option == ORDER_SHIP_ALL_TOGETHER ) {
							$block_params['shipment'] = $this->Application->Phrase('lu_all_available_backordered');
						}
						else {
							$block_params['shipment'] = $this->Application->Phrase('lu_ship_all_available');;
						}
						break;

					case 1:
						$block_params['shipment'] = $this->Application->Phrase('lu_ship_all_backordered');;
						break;

					default:
						$block_params['shipment'] = $this->Application->Phrase('lu_ship_backordered');
						break;
				}

				$block_params['promo_weight_metric'] = $shipment['TotalWeightPromo'];
				$block_params['promo_amount'] = $shipment['TotalAmountPromo'];
				$block_params['promo_items'] = $shipment['TotalItemsPromo'];

				$block_params['weight_metric'] = $shipment['TotalWeight'];
				$block_params['weight'] = $shipment['TotalWeight'];

				if ( $block_params['weight_metric'] == '' ) {
					$block_params['weight'] = $this->Application->Phrase('lu_NotAvailable');
				}
				else {
					$block_params['weight'] = $this->_formatWeight( $block_params['weight'] );
				}


				$block_params['items'] = $shipment['TotalItems'];

				$amount = $this->ConvertCurrency($shipment['TotalAmount'], $this->GetISO( $params['currency'] ));
				$amount = sprintf("%.2f", $amount);

				$block_params['amount'] = $shipment['TotalAmount'];
				$block_params['field_name'] = $this->InputName( Array('field' => 'ShippingTypeId') ) . '[' . $group . ']';

				$parsed_block = $this->Application->ParseBlock($block_params);

				if ( $this->Application->GetVar('ItemShipmentsExists') ) {
					$o .= $parsed_block;
				}
				else {
					$this->Application->SetVar('ShipmentsExists', 0);

					if ( getArrayValue($params, 'no_shipments_render_as') ) {
						$block_params['name'] = $params['no_shipments_render_as'];

						return $this->Application->ParseBlock($block_params);
					}
				}

				$group++;
			}

			if ( getArrayValue($params, 'table_header_render_as') ) {
				$o = $this->Application->ParseBlock(Array ('name' => $params['table_header_render_as'])) . $o;
			}

			if ( getArrayValue($params, 'table_footer_render_as') ) {
				$o .= $this->Application->ParseBlock(Array ('name' => $params['table_footer_render_as']));
			}

			return $o;
		}

		/**
		 * Checks, that all given address fields are valid
		 *
		 * @param Array $params
		 * @return bool
		 */
		function AddressValid($params)
		{
			/** @var kDBItem $object */
			$object = $this->getObject($params);

			$address_type = isset($params['type']) ? strtolower($params['type']) : 'shipping';
			$address_type = ucfirst($address_type);

			$check_fields = Array ('Address1', 'City', 'Zip', 'Country');

			foreach ($check_fields as $check_field) {
				if ( $object->GetDBField($address_type . $check_field) == '' ) {
					return false;
				}
			}

			return true;
		}

		function GetShippingLimitations($ord_id)
		{
			$limit_types = false;
			$types_index = $split_shipments = $cat_limitations = Array ();

			$sql = 'SELECT p.ShippingLimitation, p.ShippingMode, oi.ProductId AS ProductId
					FROM ' . TABLE_PREFIX . 'Products p
					LEFT JOIN ' . TABLE_PREFIX . 'OrderItems AS oi ON oi.ProductId = p.ProductId
					WHERE oi.OrderId = ' . $ord_id . ' AND p.Type = ' . PRODUCT_TYPE_TANGIBLE;
			$limitations = $this->Conn->Query($sql, 'ProductId');

			// group products by shipping type range and calculate intersection of all types available for ALL products
			// the intersection calculation is needed to determine if the order can be shipped with single type or not

			if ($limitations) {
				foreach ($limitations as $product_id => $row) {
					// if shipping types are limited - get the types
					$types = $row['ShippingLimitation'] != '' ? explode('|', substr($row['ShippingLimitation'], 1, -1)) : Array ('ANY');

					// if shipping is NOT limited to selected types (default - so products with no limitations at all also counts)
					if ($row['ShippingMode'] == PRODUCT_SHIPPING_MODE_ANY_AND_SELECTED) {
						array_push($types, 'ANY'); // can be shipped with ANY (literally) type
						$types = array_unique($types);
					}

					//adding product id to split_shipments group by types range
					$i = array_search(serialize($types), $types_index);
					if ($i === false) {
						$types_index[] = serialize($types);
						$i = count($types_index) - 1;
					}

					$split_shipments[$i]['Products'][] = $product_id;
					$split_shipments[$i]['Types'] = serialize($types);

					if ($limit_types === false) {
						// it is false only when we process first item with limitations
						$limit_types = $types; // initial scope
					}

					// this is to avoid ANY intersect CUST_1 = (), but allows ANY intersect CUST_1,ANY = (ANY)
					if ( in_array('ANY', $limit_types) && !in_array('ANY', $types) ) {
						array_splice($limit_types, array_search('ANY', $limit_types), 1, $types);
					}

					// this is to avoid CUST_1 intersect ANY = (), but allows CUST_1 intersect CUST_1,ANY = (ANY)
					if ( !in_array('ANY', $limit_types) && in_array('ANY', $types) ) {
						array_splice($types, array_search('ANY', $types), 1, $limit_types);
					}

					$limit_types = array_intersect($limit_types, $types);
				}

				$limit_types = count($limit_types) > 0 ? serialize(array_unique($limit_types)) : 'NONE';
			}

			return Array ($split_shipments, $limit_types);
		}

		function PaymentTypeForm($params)
		{
			$object = $this->getObject($params);

			$payment_type_id = $object->GetDBField('PaymentType');
			if($payment_type_id)
			{
				$this->Application->SetVar('pt_id', $payment_type_id);
				$block_params['name'] = $this->SelectParam($params, $this->UsingCreditCard($params) ? 'cc_render_as,block_cc' : 'default_render_as,block_default' );
				return $this->Application->ParseBlock($block_params);
			}
			return '';
		}

		/**
		 * Returns true in case if credit card was used as payment type for order.
		 *
		 * @param array $params Tag params.
		 *
		 * @return boolean
		 * @throws Exception When payment type not found.
		 */
		protected function UsingCreditCard(array $params)
		{
			static $payment_types;

			if ( !isset($payment_types) ) {
				$pt_table = $this->Application->getUnitOption('pt', 'TableName');
				$sql = 'SELECT g.RequireCCFields, pt.PaymentTypeId
					FROM ' . $pt_table . ' pt
					JOIN ' . TABLE_PREFIX . 'Gateways g ON g.GatewayId = pt.GatewayId';

				$payment_types = $this->Conn->GetCol($sql, 'PaymentTypeId');
			}

			/** @var kDBItem $object */
			$object = $this->getObject($params);
			$payment_type = $object->GetDBField('PaymentType');

			if ( !$payment_type ) {
				$payment_type = $this->getPrimaryPaymentType();
				$object->SetDBField('PaymentType', $payment_type);
			}

			if ( !isset($payment_types[$payment_type]) ) {
				throw new Exception('Unknown payment type: ' . $payment_type);
			}

			return $payment_types[$payment_type];
		}

		/**
		 * Get primary payment type.
		 *
		 * @return string
		 */
		protected function getPrimaryPaymentType()
		{
			static $primary_payment_type;

			if ( !isset($primary_payment_type) ) {
				$pt_table = $this->Application->getUnitOption('pt', 'TableName');
				$sql = 'SELECT PaymentTypeId FROM ' . $pt_table . ' WHERE IsPrimary = 1';
				$primary_payment_type = $this->Conn->GetOne($sql);
			}

			return $primary_payment_type;
		}

		function PaymentTypeDescription($params)
		{
			return $this->Application->ProcessParsedTag('pt', 'Field', 	array_merge($params, Array(
					'field' => 'Description'
			)) );
		}

		function PaymentTypeInstructions($params)
		{
			return $this->Application->ProcessParsedTag('pt', 'Field', 	array_merge($params, Array(
					'field' => 'Instructions'
			)) );
		}

		function PrintMonthOptions($params)
		{
			$object = $this->getObject($params);

			$date = explode('/', $object->GetDBField($params['date_field_name']));
			if (!$date || sizeof($date) != 2) {
				$date=array("", "");
			}
			$o = '';
			$params['name'] = $params['block'];
			for ($i = 1; $i <= 12; $i++) {
				$month_str = str_pad($i, 2, "0", STR_PAD_LEFT);
				if ($date[0] == $month_str) {
					$params['selected'] = ' selected';
				}else {
					$params['selected'] = '';
				}

				$params['mm'] = $month_str;
				$o .= $this->Application->ParseBlock($params);
			}
			return $o;
		}

		function PrintYearOptions($params)
		{
			$object = $this->getObject($params);
			$value = $object->GetDBField( $params['field'] );

			$block_params = $this->prepareTagParams($params);
			$block_params['name'] = $this->SelectParam($params, 'render_as,block');

			$o = '';
			$this_year = adodb_date('y');

			for($i = $this_year; $i <= $this_year + 10; $i++)
			{
				$year_str = str_pad($i, 2, '0', STR_PAD_LEFT);
				$block_params['selected'] = ($value == $year_str) ? $params['selected'] : '';
				$block_params['key'] = $year_str;
				$block_params['option'] = $year_str;
				$o .= $this->Application->ParseBlock($block_params);
			}
			return $o;
		}

		function PrintMyOrders($params)
		{

		}

		/**
		 * Checks, that order data can be editied based on it's status
		 *
		 * @param Array $params
		 * @return bool
		 */
		function OrderEditable($params)
		{
			$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
			$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');

			if ($this->Application->IsTempMode($this->Prefix, $this->Special)) {
				$table_name = $this->Application->GetTempName($table_name, 'prefix:' . $this->Prefix);
			}

			// use direct select here (not $this->getObject) because this tag is
			// used even before "combined_header" block is used (on "orders_edit_items" template)
			$sql = 'SELECT Status, PaymentType
					FROM ' . $table_name . '
					WHERE ' . $id_field . ' = ' . $this->Application->GetVar( $this->getPrefixSpecial() . '_id' );
			$order_data = $this->Conn->GetRow($sql);

			if (!$order_data) {
				// new order adding, when even not in database
				return true;
			}

			switch ($order_data['Status']) {
				case ORDER_STATUS_INCOMPLETE:
					$ret = true;
					break;

				case ORDER_STATUS_PENDING:
				case ORDER_STATUS_BACKORDERS:
					$sql = 'SELECT PlacedOrdersEdit
							FROM ' . $this->Application->getUnitOption('pt', 'TableName') . '
							WHERE ' . $this->Application->getUnitOption('pt', 'IDField') . ' = ' . $order_data['PaymentType'];
					$ret = $this->Conn->GetOne($sql);
					break;

				default:
					$ret = false;
					break;
			}

			return $ret;
		}

		function CheckoutSteps($params)
		{
			$steps = explode(',', $params['steps']);
			foreach ($steps as $key => $item)
			{
				$templates[$key] = trim($item);
			}

			$templates = explode(',', $params['templates']);
			foreach ($templates as $key => $item)
			{
				$templates[$key] = trim($item);
			}
			$total_steps = count($templates);
			$t = $this->Application->GetVar('t');

			$o = '';
			$block_params = array();
			$i = 0;
			$passed_current = preg_match("/".preg_quote($templates[count($templates)-1], '/')."/", $t);
			foreach ($steps as $step => $name)
			{
				if (preg_match("/".preg_quote($templates[$step], '/')."/", $t)) {
					$block_params['name'] = $this->SelectParam($params, 'current_step_render_as,block_current_step');
					$passed_current = true;
				}
				else {
					$block_params['name'] = $passed_current ? $this->SelectParam($params, 'render_as,block') : $this->SelectParam($params, 'passed_step_render_as,block_passed_step');
				}
				$block_params['title'] = $this->Application->Phrase($name);
				$block_params['template'] = $templates[$i];
				$block_params['template_link'] = $this->Application->HREF($templates[$step], '', Array('pass'=>'m'));
				$block_params['next_step_template'] = isset($templates[$i + 1]) ? $templates[$i + 1] : '';
				$block_params['number'] = $i + 1;
				$i++;
				$o.= $this->Application->ParseBlock($block_params, 1);
			}
			return $o;
		}

		function ShowOrder($params)
		{

			$order_params = $this->prepareTagParams($params);
//			$order_params['Special'] = 'myorders';
//			$order_params['PrefixSpecial'] = 'ord.myorders';
			$order_params['name'] = $this->SelectParam($order_params, 'render_as,block');
//			$this->Application->SetVar('ord.myorders_id', $this->Application->GetVar('ord_id'));

			$object = $this->getObject($params);
			if (!$object->GetDBField('OrderId')) {
				return;
			}
			return $this->Application->ParseBlock($order_params);
		}

		function BuildListSpecial($params)
		{
			if ($this->Special != '') {
				return $this->Special;
			}

			$list_unique_key = $this->getUniqueListKey($params);
			if ($list_unique_key == '') {
				return parent::BuildListSpecial($params);
			}

			return crc32($list_unique_key);
		}

		function ListOrders($params)
		{
			$o = '';
			$params['render_as'] = $params['item_render_as'];

			$o_orders = $this->PrintList2($params);

			if ($o_orders) {
				$orders_params = array('name' => $params['header_render_as']);
				$o  = $this->Application->ParseBlock($orders_params);
				$o .= $o_orders;

			} else {
				$orders_params = array('name' => $params['empty_myorders_render_as']);
				$o = $this->Application->ParseBlock($orders_params);
			}

			return $o;

		}

		function HasRecentOrders($params)
		{
			$per_page = $this->SelectParam($params, 'per_page,max_items');
			if ($per_page !== false) {
				$params['per_page'] = $per_page;
			}

			return (int)$this->TotalRecords($params) > 0 ? 1 : 0;
		}

		function ListOrderItems($params)
		{
			$prefix_special = rtrim('orditems.'.$this->Special, '.');
			return $this->Application->ProcessParsedTag($prefix_special, 'PrintList', 	array_merge($params, Array(
					'per_page' => -1
			)) );
		}


		function OrdersLink(){
			$params['pass']='m,ord';
			$main_processor = $this->Application->recallObject('m_TagProcessor');
			return $main_processor->Link($params);
		}



		function PrintAddresses($params)
		{
			$object = $this->getObject($params);

			$address_list = $this->Application->recallObject('addr','addr_List', Array('per_page'=>-1, 'skip_counting'=>true) );
			$address_list->Query();

			$address_id = $this->Application->GetVar($params['type'].'_address_id');
			if (!$address_id) {
				$sql = 'SELECT '.$address_list->IDField.'
						FROM '.$address_list->TableName.'
						WHERE PortalUserId = '.$object->GetDBField('PortalUserId').' AND LastUsedAs'.ucfirst($params['type']).' = 1';
				$address_id = (int)$this->Conn->GetOne($sql);
			}

			$ret = '';
			$block_params = $this->prepareTagParams($params);
			$block_params['name'] = $this->SelectParam($params, 'render_as,block');

			$address_list->GoFirst();
			while (!$address_list->EOL()) {

				$selected = ($address_list->GetID() == $address_id);
				if ($selected && $address_list->GetDBField('IsProfileAddress')) {
					$this->Application->SetVar($this->Prefix.'_IsProfileAddress', true);
				}

				$block_params['key'] = $address_list->GetID();
				$block_params['value'] = $address_list->GetDBField('ShortAddress');
				$block_params['selected'] = $selected ? ' selected="selected"' : '';

				$ret .= $this->Application->ParseBlock($block_params, 1);
				$address_list->GoNext();
			}

			return $ret;
		}

		function PrefillRegistrationFields($params)
		{
			if ( $this->Application->GetVar('fields_prefilled') ) {
				return false;
			}

			if ( isset($params['user_prefix']) ) {
				/** @var kDBItem $user */
				$user = $this->Application->recallObject($params['user_prefix']);
			}
			else {
				/** @var kDBItem $user */
				$user = $this->Application->recallObject('u', null, Array ('skip_autoload' => true));
			}

			/** @var OrdersItem $order */
			$order = $this->Application->recallObject($this->Prefix . '.last');

			/** @var OrderHelper $order_helper */
			$order_helper = $this->Application->recallObject('OrderHelper');

			$user_fields = $order_helper->getUserFields($order, $params['type'] == 'billing' ? 'Billing' : 'Shipping');

			foreach ($user_fields as $field => $value) {
				if ( !$user->GetDBField($field) ) {
					$user->SetDBField($field, $value);
				}
			}

			/** @var kCountryStatesHelper $cs_helper */
			$cs_helper = $this->Application->recallObject('CountryStatesHelper');

			$cs_helper->PopulateStates(new kEvent('u:OnBuild'), 'State', 'Country');
		}

		function UserLink($params)
		{
			$object = $this->getObject($params);
			$user_id = $object->GetDBField( $params['user_field'] );

			if ($user_id) {
				$url_params =  Array (
					'm_opener' => 'd',
					'u_mode' => 't',
					'u_event' => 'OnEdit',
					'u_id' => $user_id,
					'pass' => 'all,u',
					'no_pass_through' => 1,
				);

				return $this->Application->HREF($params['edit_template'], '', $url_params);
			}
		}

		function UserFound($params)
		{
			$virtual_users = Array(USER_ROOT, USER_GUEST, 0);
			$object = $this->getObject($params);
			return !in_array( $object->GetDBField( $params['user_field'] ) , $virtual_users );
		}

		/**
		 * Returns a link for editing order
		 *
		 * @param Array $params
		 * @return string
		 */
		function OrderLink($params)
		{
			$object = $this->getObject($params);

			$url_params = Array (
				'm_opener' => 'd',
				$this->Prefix.'_mode' => 't',
				$this->Prefix.'_event' => 'OnEdit',
				$this->Prefix.'_id' => $object->GetID(),
				'pass' => 'all,'.$this->Prefix,
				'no_pass_through' => 1,
			);

			return $this->Application->HREF($params['edit_template'], '', $url_params);
		}

		function HasOriginalAmount($params)
		{
			$object = $this->getObject($params);
			$original_amount = $object->GetDBField('OriginalAmount');
			return $original_amount && ($original_amount != $object->GetDBField('TotalAmount') );
		}

		/**
		 * Returns true, when order has tangible items
		 *
		 * @param Array $params
		 * @return bool
		 *
		 * @todo This is copy from OrdersItem::HasTangibleItems. Copy to helper (and create it) and use here.
		 */
		function OrderHasTangibleItems($params)
		{
			$object = $this->getObject($params);
			if ($object->GetID() == FAKE_ORDER_ID) {
				return false;
			}

			$sql = 'SELECT COUNT(*)
					FROM '.TABLE_PREFIX.'OrderItems orditems
					LEFT JOIN '.TABLE_PREFIX.'Products p ON p.ProductId = orditems.ProductId
					WHERE (orditems.OrderId = '.$object->GetID().') AND (p.Type = '.PRODUCT_TYPE_TANGIBLE.')';
			return $this->Conn->GetOne($sql) ? true : false;
		}

		function ShipmentsExists($params)
		{
			return $this->Application->GetVar('ShipmentsExists') ? 1 : 0;
		}

		function Field($params)
		{
			$value = parent::Field($params);
			$field = $this->SelectParam($params,'name,field');
			if( ($field == 'PaymentAccount') && getArrayValue($params,'masked') )
			{
				$value = str_repeat('X',12).substr($value,-4);
			}
			return $value;
		}

		function CartHasError($params)
		{
			return $this->Application->RecallVar('checkout_errors');
		}

		function CheckoutError($params)
		{
			$errors = $this->Application->RecallVar('checkout_errors');

			if ( !$errors ) {
				return '';
			}

			$this->Application->RemoveVar('checkout_errors');
			$errors = unserialize($errors);

			if ( isset($errors[OrderCheckoutErrorType::COUPON]) ) {
				$mapping = Array (
					OrderCheckoutError::COUPON_APPLIED => 'coupon_applied',
					OrderCheckoutError::COUPON_REMOVED => 'code_removed',
					OrderCheckoutError::COUPON_REMOVED_AUTOMATICALLY => 'code_removed_automatically',
					OrderCheckoutError::COUPON_CODE_INVALID => 'invalid_code',
					OrderCheckoutError::COUPON_CODE_EXPIRED => 'code_expired',
				);

				$error_phrase = $mapping[ $errors[OrderCheckoutErrorType::COUPON] ];
			}
			elseif ( isset($errors[OrderCheckoutErrorType::GIFT_CERTIFICATE]) ) {
				$mapping = Array (
					OrderCheckoutError::GC_APPLIED => 'gift_certificate_applied',
					OrderCheckoutError::GC_REMOVED => 'gc_code_removed',
					OrderCheckoutError::GC_REMOVED_AUTOMATICALLY => 'gc_code_removed_automatically',
					OrderCheckoutError::GC_CODE_INVALID => 'invalid_gc_code',
					OrderCheckoutError::GC_CODE_EXPIRED => 'gc_code_expired',

				);

				$error_phrase = $mapping[ $errors[OrderCheckoutErrorType::GIFT_CERTIFICATE] ];
			}
			else {
				$mapping = Array (
					OrderCheckoutError::QTY_UNAVAILABLE => 'qty_unavailable',
					OrderCheckoutError::QTY_OUT_OF_STOCK => 'outofstock',
					OrderCheckoutError::QTY_CHANGED_TO_MINIMAL => 'min_qty',
				);

				foreach ($errors as $error_type => $error_code) {
					if ( isset($mapping[$error_code]) ) {
						$error_phrase = $mapping[$error_code];
						break;
					}
				}

				if ( !isset($error_phrase) ) {
					$error_phrase = 'state_changed'; // 'changed_after_login'
				}
			}

			return $this->Application->Phrase( $params[$error_phrase] );
		}

		/**
		 * Returns checkout errors in JSON format
		 *
		 * @param Array $params
		 * @return string
		 */
		function PrintOrderInfo($params)
		{
			/** @var OrderHelper $order_helper */
			$order_helper = $this->Application->recallObject('OrderHelper');

			/** @var kDBItem $object */
			$object = $this->getObject($params);

			$currency = isset($params['currency']) ? $params['currency'] : 'selected';

			return json_encode( $order_helper->getOrderInfo($object, $currency) );
		}

		/**
		 * Returns currency mark (%s is $amount placemark)
		 *
		 * @param Array $params
		 * @return string
		 */
		function CurrencyMask($params)
		{
			$iso = $this->GetISO( $params['currency'] );

			return $this->AddCurrencySymbol('%s', $iso);
		}

		function CheckoutErrorNew($params)
		{
			$errors = $this->Application->RecallVar('checkout_errors');

			if ( !$errors ) {
				return '';
			}

//			$this->Application->RemoveVar('checkout_errors');
			$errors = unserialize($errors);

			$reflection = new ReflectionClass('OrderCheckoutErrorType');
			$error_types = $reflection->getConstants();

			$reflection = new ReflectionClass('OrderCheckoutError');
			$error_codes = $reflection->getConstants();

			$ret = Array ();

			foreach ($errors as $error_type => $error_code) {
				$error_type = explode(':', $error_type);

				$error_explained = '<strong>' . array_search($error_type[0], $error_types) . '</strong>';

				if ( $error_type[0] == OrderCheckoutErrorType::PRODUCT ) {
					$error_explained .= ' (ProductId = <strong>' . $error_type[1] . '</strong>; OptionsSalt: <strong>' . $error_type[2] . '</strong>; BackOrderFlag: ' . $error_type[3] . '; Field: <strong>' . $error_type[4] . '</strong>)';
				}

				$error_explained .= ' - <strong>' . array_search($error_code, $error_codes) . '</strong>';
				$ret[] = $error_explained;
			}

			return implode('<br/>', $ret);
		}

		function GetFormAction($params)
		{
			/** @var OrdersItem $object */
			$object = $this->getObject($params);

			$gw_data = $object->getGatewayData( isset($params['payment_type_id']) ? $params['payment_type_id'] : null );

			/** @var kGWBase $gateway_object */
			$gateway_object = $this->Application->recallObject($gw_data['ClassName']);

			return $gateway_object->getFormAction($gw_data['gw_params']);
		}

		function GetFormHiddenFields($params)
		{
			/** @var OrdersItem $object */
			$object = $this->getObject($params);

			$gw_data = $object->getGatewayData( isset($params['payment_type_id']) ? $params['payment_type_id'] : null );

			/** @var kGWBase $gateway_object */
			$gateway_object = $this->Application->recallObject($gw_data['ClassName']);

			$tpl = '<input type="hidden" name="%s" value="%s" />'."\n";
			$hidden_fields = $gateway_object->getHiddenFields($object->GetFieldValues(), $params, $gw_data['gw_params']);

			if ( !is_array($hidden_fields) ) {
				return $hidden_fields;
			}

			$ret = '';

			foreach ($hidden_fields as $hidden_name => $hidden_value) {
				$ret .= sprintf($tpl, $hidden_name, $hidden_value);
			}

			return $ret;
		}

		function NeedsPlaceButton($params)
		{
			/** @var OrdersItem $object */
			$object = $this->getObject($params);

			$gw_data = $object->getGatewayData( isset($params['payment_type_id']) ? $params['payment_type_id'] : null );

			/** @var kGWBase $gateway_object */
			$gateway_object = $this->Application->recallObject($gw_data['ClassName']);

			return $gateway_object->NeedPlaceButton($object->GetFieldValues(), $params, $gw_data['gw_params']);
		}

		function HasGatewayError($params)
		{
			return $this->Application->RecallVar('gw_error');
		}

		function ShowGatewayError($params)
		{
			$ret = $this->Application->RecallVar('gw_error');
			$this->Application->RemoveVar('gw_error');
			return $ret;
		}

		function ShippingType($params)
		{
			/** @var kDBItem $object */
			$object = $this->getObject($params);

			$shipping_info = unserialize($object->GetDBField('ShippingInfo'));

			if ( count($shipping_info) > 1 ) {
				return $this->Application->Phrase('lu_MultipleShippingTypes');
			}

			if ( $shipping_info ) {
				$shipping_info = array_shift($shipping_info);

				return $shipping_info['ShippingName'];
			}

			return '';
		}

		function DiscountHelpLink($params)
		{
			$params['pass'] = 'all,orditems';
			$params['m_cat_id'] = 0;
			$m_tag_processor = $this->Application->recallObject('m_TagProcessor');
			return $m_tag_processor->Link($params);
		}

		function DiscountField($params)
		{
			$orditems = $this->Application->recallObject( 'orditems' );
			$item_data = $orditems->GetDBField('ItemData');
			if(!$item_data) return '';
			$item_data = unserialize($item_data);
			$discount_prefix = ($item_data['DiscountType'] == 'coupon') ? 'coup' : 'd';

			$discount = $this->Application->recallObject($discount_prefix, null, Array('skip_autoload' => true));
			if(!$discount->isLoaded())
			{
				$discount->Load($item_data['DiscountId']);
			}
			return $discount->GetField( $this->SelectParam($params, 'field,name') );
		}

		function HasDiscount($params)
		{
			$object = $this->getObject($params);
			return (float)$object->GetDBField('DiscountTotal') ? 1 : 0;
		}

		/**
		 * Allows to check if required product types are present in order
		 *
		 * @param Array $params
		 */
		function HasProductType($params)
		{
			$product_types = Array('tangible' => 1, 'subscription' => 2, 'service' => 3, 'downloadable' => 4, 'package' => 5, 'gift' => 6);
			$object = $this->getObject($params);

			$sql = 'SELECT COUNT(*)
					FROM '.TABLE_PREFIX.'OrderItems oi
					LEFT JOIN '.TABLE_PREFIX.'Products p ON p.ProductId = oi.ProductId
					WHERE (oi.OrderId = '.$object->GetID().') AND (p.Type = '.$product_types[ $params['type'] ].')';
			return $this->Conn->GetOne($sql);
		}

		function PrintSerializedFields($params)
		{
			$object = $this->getObject($params);
			$field = $this->SelectParam($params, 'field');
			if (!$field) $field = $this->Application->GetVar('field');
			$data = unserialize($object->GetDBField($field));

			$o = '';
			$block_params['name'] = $params['render_as'];
			foreach ($data as $field => $value) {
				$block_params['field'] = $field;
				$block_params['value'] = $value;
				$o .= $this->Application->ParseBlock($block_params);
			}
			return $o;
		}

		/**
		 * Prints order totals
		 *
		 * @param Array $params
		 * @return string
		 * @access protected
		 */
		protected function PrintTotals($params)
		{
			/** @var OrdersItem $object */
			$object = $this->getObject($params);

			if ( isset($params['element_order']) ) {
				// TODO: implement
			}
			else {
				// default element order
				$element_order = Array (
					'products' => 1, 'return' => 4, 'sub_total' => 5, 'discount' => 6,
					'vat' => 7, 'shipping' => 8, 'processing' => 9,
				);

				// show shipping & processing costs before tax, when they are taxable
				if ( $object->GetDBField('ShippingTaxable') ) {
					$element_order['shipping'] = 2;
				}

				if ( $object->GetDBField('ProcessingTaxable') ) {
					$element_order['processing'] = 3;
				}
			}

			$totals = Array ();

			if ( abs($object->GetDBField('SubTotal') - $object->GetDBField('AmountWithoutVAT')) > 0.01 ) {
				$totals[] = 'products';
			}

			if ( $this->OrderHasTangibleItems($params) ) {
				$totals[] = 'shipping';
			}

			if ( $object->GetDBField('ProcessingFee') > 0 ) {
				$totals[] = 'processing';
			}

			if ( $object->GetDBField('ReturnTotal') > 0 ) {
				$totals[] = 'return';
			}

			if ( $this->HasDiscount($params) ) {
				$totals[] = 'discount';
			}

			$totals[] = 'sub_total';

			if ( $object->GetDBField('VAT') > 0 ) {
				$totals[] = 'vat';
			}

			$o = '';
			asort($element_order, SORT_NUMERIC);

			$block_params = $this->prepareTagParams($params);

			foreach ($element_order as $type => $order) {
				$element = getArrayValue($params, $type . '_render_as');

				if ( !in_array($type, $totals) || !$element ) {
					continue;
				}

				$block_params['name'] = $element;

				$o .= $this->Application->ParseBlock($block_params);
			}

			return $o;
		}

		function ShowDefaultAddress($params)
		{
			$address_type = ucfirst($params['type']);
			if ($this->Application->GetVar('check_'.strtolower($address_type).'_address')) {
				// form type doesn't match check type, e.g. shipping check on billing form
				return '';
			}

			// for required field highlighting on form when no submit made
			$this->Application->SetVar('check_'.strtolower($address_type).'_address', 'true');
			/*if ((strtolower($address_type) == 'billing') && $this->UsingCreditCard($params)) {
				$this->Application->SetVar('check_credit_card', 'true');
			}*/

			$this->Application->HandleEvent(new kEvent('ord:SetStepRequiredFields'));

			$user_id = $this->Application->RecallVar('user_id');
			$sql = 'SELECT AddressId
					FROM '.TABLE_PREFIX.'Addresses
					WHERE PortalUserId = '.$user_id.' AND LastUsedAs'.$address_type.' = 1';
			$address_id = $this->Conn->GetOne($sql);
			if (!$address_id) {
				return '';
			}

			$addr_list = $this->Application->recallObject('addr', 'addr_List', Array('per_page'=>-1, 'skip_counting'=>true) );
			$addr_list->Query();

			$object = $this->getObject();
			if (!$addr_list->CheckAddress($object->GetFieldValues(), $address_type)) {
				$addr_list->CopyAddress($address_id, $address_type);
			}
		}

		function IsProfileAddress($params)
		{
			$object = $this->getObject($params);
			$address_type = ucfirst($params['type']);
			return $object->IsProfileAddress($address_type);
		}

		function HasPayPalSubscription($params)
		{
			$object = $this->getObject($params);

			$sql = 'SELECT COUNT(*)
					FROM '.TABLE_PREFIX.'OrderItems oi
					LEFT JOIN '.TABLE_PREFIX.'Products p ON p.ProductId = oi.ProductId
					WHERE (oi.OrderId = '.$object->GetID().') AND (p.PayPalRecurring = 1)';
			return $this->Conn->GetOne($sql);

		}

		function GetPayPalSubscriptionForm($params)
		{
			$object = $this->getObject($params);
			$gw_data = $object->getGatewayData($params['payment_type_id']);

			/** @var kGWBase $gateway_object */
			$gateway_object = $this->Application->recallObject($gw_data['ClassName']);

			$sql = 'SELECT oi.*
					FROM '.TABLE_PREFIX.'OrderItems oi
					LEFT JOIN '.TABLE_PREFIX.'Products p ON p.ProductId = oi.ProductId
					WHERE (oi.OrderId = '.$object->GetID().') AND (p.PayPalRecurring = 1)';
			$order_item = $this->Conn->GetRow($sql);
			$order_item_data =  unserialize($order_item['ItemData']);

			$cycle = ceil($order_item_data['Duration'] / 86400);
			$cycle_units = 'D';

			$item_data = $object->GetFieldValues();
			$item_data['item_name'] = $order_item['ProductName'];
			$item_data['item_number'] = $order_item['OrderItemId'];
			$item_data['custom'] = $order_item['OrderId'];
			$item_data['a1'] = '';
			$item_data['p1'] = '';
			$item_data['t1'] = '';
			$item_data['a2'] = '';
			$item_data['p2'] = '';
			$item_data['t2'] = '';
			$item_data['a3'] = $order_item['Price']; //rate
			$item_data['p3'] = $cycle; //cycle
			$item_data['t3'] = $cycle_units; //cycle units D (days), W (weeks), M (months), Y (years)
			$item_data['src'] = '1'; // Recurring payments. If set to 1, the payment will recur unless your customer cancels the subscription before the end of the billing cycle.
			$item_data['sra'] = '1'; // Reattempt on failure. If set to 1, and the payment fails, the payment will be reattempted two more times. After the third failure, the subscription will be cancelled.
			$item_data['srt'] = ''; // Recurring Times. This is the number of payments which will occur at the regular rate.

			$hidden_fields = $gateway_object->getSubscriptionFields($item_data, $params, $gw_data['gw_params']);

			$ret = '';
			if (!is_array($hidden_fields)) {
				return $hidden_fields;
			}
			$tpl = '<input type="hidden" name="%s" value="%s" />'."\n";
			foreach($hidden_fields as $hidden_name => $hidden_value)
			{
				$ret .= sprintf($tpl, $hidden_name, $hidden_value);
			}
			return $ret;
		}

		function UserHasPendingOrders($params)
		{
			$sql = 'SELECT OrderId FROM '.$this->Application->getUnitOption($this->Prefix, 'TableName').'
							WHERE PortalUserId = '.$this->Application->RecallVar('user_id').'
							AND Status = '.ORDER_STATUS_PENDING;
			return $this->Conn->GetOne($sql) ? 1 : 0;
		}

		function AllowAddAddress($params)
		{
			$user = $this->Application->recallObject('u.current');

			if ($user->GetDBField('cust_shipping_addr_block')) return false;

			$address_list = $this->Application->recallObject('addr','addr_List', Array('per_page'=>-1, 'skip_counting'=>true) );
			$address_list->Query();

			$max = $this->Application->ConfigValue('MaxAddresses');

			return $max <= 0 ? true : $address_list->GetRecordsCount() < $max;
		}

		/**
		 * Creates link for removing coupon or gift certificate
		 *
		 * @param Array $params
		 * @return string
		 */
		function RemoveCouponLink($params)
		{
			if ( !isset($params['pass']) ) {
				$params['pass'] = 'm,ord';
			}

			$params['m_cat_id'] = 0;

			$link_type = $params['type'];
			unset($params['type']);

			$params['ord_event'] = strtolower($link_type) == 'coupon' ? 'OnRemoveCoupon' : 'OnRemoveGiftCertificate';

			return $this->ItemLink($params);
		}

		/**
		 * Calculates total weight of items in shopping cart
		 *
		 * @param Array $params
		 * @return float
		 */
		function TotalOrderWeight($params)
		{
			/** @var kDBItem $object */
			$object = $this->getObject($params);

			$sql = 'SELECT SUM( IF(oi.Weight IS NULL, 0, oi.Weight * oi.Quantity) )
					FROM '.TABLE_PREFIX.'OrderItems oi
					WHERE oi.OrderId = '.$object->GetID();
			$total_weight = $this->Conn->GetOne($sql);

			if ($total_weight == '') {
				// zero weight -> return text about it
				return $this->Application->Phrase('lu_NotAvailable');
			}

			return $this->_formatWeight($total_weight);
		}

		function _formatWeight($weight)
		{
			/** @var kDBItem $regional */
			$regional = $this->Application->recallObject('lang.current');

			switch ( $regional->GetDBField('UnitSystem') ) {
				case 1:
					// metric system -> add kg sign
					$weight .= ' ' . $this->Application->Phrase('lu_kg');
					break;

				case 2:
					// uk system -> convert to pounds
					list ($pounds, $ounces) = kUtil::Kg2Pounds($weight);

					$weight = $pounds . ' ' . $this->Application->Phrase('lu_pounds') . ' ' . $ounces . ' ' . $this->Application->Phrase('lu_ounces');
					break;
			}

			return $weight;
		}

		function InitCatalogTab($params)
		{
			$tab_params['mode'] = $this->Application->GetVar('tm'); // single/multi selection possible
			$tab_params['special'] = $this->Application->GetVar('ts'); // use special for this tab
			$tab_params['dependant'] = $this->Application->GetVar('td'); // is grid dependant on categories grid

			// set default params (same as in catalog)
			if ($tab_params['mode'] === false) $tab_params['mode'] = 'multi';
			if ($tab_params['special'] === false) $tab_params['special'] = '';
			if ($tab_params['dependant'] === false) $tab_params['dependant'] = 'yes';

			// pass params to block with tab content
			$params['name'] = $params['render_as'];
			$params['prefix'] = trim($this->Prefix.'.'.($tab_params['special'] ? $tab_params['special'] : $this->Special), '.');
			$params['cat_prefix'] = trim('c.'.($tab_params['special'] ? $tab_params['special'] : $this->Special), '.');
			$params['tab_mode'] = $tab_params['mode'];
			$params['grid_name'] = ($tab_params['mode'] == 'multi') ? $params['default_grid'] : $params['radio_grid'];
			$params['tab_dependant'] = $tab_params['dependant'];
			$params['show_category'] = $tab_params['special'] == 'showall' ? 1 : 0; // this is advanced view -> show category name

			// use $pass_params to be able to pass 'tab_init' parameter from m_ModuleInclude tag
			return $this->Application->ParseBlock($params, 1);
		}

		/**
		 * Checks if required payment method is available
		 *
		 * @param Array $params
		 * @return bool
		 */
		function HasPaymentGateway($params)
		{
			static $payment_types = Array ();

			$gw_name = $params['name'];
			if (!array_key_exists($gw_name, $payment_types)) {
				$sql = 'SELECT pt.PaymentTypeId, pt.PortalGroups
						FROM '.TABLE_PREFIX.'PaymentTypes pt
						LEFT JOIN '.TABLE_PREFIX.'Gateways g ON pt.GatewayId = g.GatewayId
						WHERE (g.Name = '.$this->Conn->qstr($params['name']).') AND (pt.Status = '.STATUS_ACTIVE.')';
				$payment_types[$gw_name] = $this->Conn->GetRow($sql);
			}

			if (!$payment_types[$gw_name]) {
				return false;
			}

			$pt_groups = explode(',', substr($payment_types[$gw_name]['PortalGroups'], 1, -1));
			$user_groups = explode(',', $this->Application->RecallVar('UserGroups'));

			return array_intersect($user_groups, $pt_groups) ? $payment_types[$gw_name]['PaymentTypeId'] : false;
		}

		function DisplayPaymentGateway($params)
		{
			$payment_type_id = $this->HasPaymentGateway($params);
			if (!$payment_type_id) {
				return '';
			}

			/** @var OrdersItem $object */
			$object = $this->getObject($params);

			$gw_data = $object->getGatewayData($payment_type_id);

			$block_params = $gw_data['gw_params'];
			$block_params['name'] = $params['render_as'];
			$block_params['payment_type_id'] = $payment_type_id;

			return $this->Application->ParseBlock($block_params);
		}

		/**
		 * Checks, that USPS returned valid label
		 *
		 * @param Array $params
		 * @return bool
		 */
		function USPSLabelFound($params)
		{
			/** @var kDBItem $object */
			$object = $this->getObject($params);

			$full_path = USPS_LABEL_FOLDER . $object->GetDBField( $params['field'] ) . '.pdf';

			return file_exists($full_path) && is_file($full_path);
		}

		/**
		 * Prints SQE errors from session
		 *
		 * @param Array $params
		 * @return string
		 */
		function PrintSQEErrors($params)
		{
			$sqe_errors = $this->Application->RecallVar('sqe_errors');

			if (!$sqe_errors) {
				return '';
			}

			$o = '';
			$block_params = $this->prepareTagParams($params);
			$block_params['name'] = $params['render_as'];
			$sqe_errors = unserialize($sqe_errors);

			foreach ($sqe_errors as $order_number => $error_description) {
				$block_params['order_number'] = $order_number;
				$block_params['error_description'] = $error_description;

				$o .= $this->Application->ParseBlock($block_params);
			}

			$this->Application->RemoveVar('sqe_errors');

			return $o;
		}

		/**
		 * Creates a continue shopping link
		 *
		 * @param Array $params
		 * @return string
		 * @access protected
		 */
		protected function ContinueShoppingLink($params)
		{
			/** @var OrderHelper $order_helper */
			$order_helper = $this->Application->recallObject('OrderHelper');

			if ( isset($params['template']) ) {
				$template = $params['template'];
				unset($params['template']);
			}
			else {
				$template = '';
			}

			return $this->Application->HREF($order_helper->getContinueShoppingTemplate($template), '', $params);
		}

		/**
		 * Checks that billing address and shipping address are the same
		 *
		 * @param Array $params
		 * @return string
		 * @access protected
		 */
		protected function AddressesTheSame($params)
		{
			/** @var kDBItem $object */
			$object = $this->getObject($params);

			$address_fields = Array ('To', 'Company', 'Address1', 'Address2', 'City', 'Country', 'State', 'Zip');

			foreach ($address_fields as $address_field) {
				if ( $object->GetDBField('Shipping' . $address_field) != $object->GetDBField('Billing' . $address_field) ) {
					return false;
				}
			}

			return true;
		}
	}
