Index: branches/5.0.x/units/orders/orders_tag_processor.php
===================================================================
--- branches/5.0.x/units/orders/orders_tag_processor.php	(revision 13272)
+++ branches/5.0.x/units/orders/orders_tag_processor.php	(revision 13273)
@@ -1,1488 +1,1489 @@
 <?php
 /**
 * @version	$Id$
 * @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') {
 					$sql = 'SELECT DestName
 							FROM '.TABLE_PREFIX.'StdDestinations
 							WHERE DestAbbr = '.$this->Conn->qstr($value);
 					$value = $this->Application->Phrase( $this->Conn->GetOne($sql) );
 				}
 
 				if ($field == 'Country' && $value) {
 					$ret .= '<br/>';
 				}
 
 				if ($value) {
 					$ret .= $value.', ';
 				}
 			}
 
 			return rtrim($ret,', ');
 		}
 
 		function Orditems_LinkRemoveFromCart($params)
 		{
 			return $this->Application->HREF($this->Application->GetVar('t'), '', Array('pass' => 'm,orditems,ord', 'ord_event' => 'OnRemoveFromCart', 'm_cat_id'=>0));
 		}
 
 		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) {
 				$cart_params = array('name' => $params['header_render_as']);
 				$o  = $this->Application->ParseBlock($cart_params);
 				$o .= $o_items;
 				$cart_params = array('name' => $params['footer_render_as']);
 				$o .= $this->Application->ParseBlock($cart_params);
 			} else {
 				$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)
 		{
 			$object =& $this->getObject($params);
 			if ($object->GetDBField('Status') != ORDER_STATUS_INCOMPLETE) {
 				return 0;
 			}
 
 			$object =& $this->Application->recallObject('orditems', 'orditems_List');
 			/* @var $object kDBList */
 
 			return $object->RecordsCount;
 		}
 
 		function CartNotEmpty($params)
 		{
 			$object =& $this->getObject($params);
 
 			if ($object->GetDBField('Status') != ORDER_STATUS_INCOMPLETE) {
 				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)
 		{
 			$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 = '';
 			$object =& $this->getObject($params);
 			$ord_id = $object->GetId();
 
 			$shipping_option = $object->GetDBField('ShippingOption');
 			$backorder_select = $shipping_option == 0 ? '0 As BackOrderFlag' : 'BackOrderFlag';
 
 			$order_items =& $this->Application->recallObject('orditems', 'orditems_List', Array('skip_autoload' => true) );
 			$oi_table = $order_items->TableName;
 
 			list($split_shipments, $limit_types) = $this->GetShippingLimitations($ord_id);
 			foreach ($split_shipments as $group => $data)
 			{
 				$query = 'UPDATE '.$oi_table.' SET SplitShippingGroup = '.$group.'
 									WHERE ProductId IN ('.implode(',', $data['Products']).')';
 				$this->Conn->Query($query);
 				$limitations_cache[$group] = $data['Types'];
 			}
 
 			$shipping_group_option = $object->GetDBField('ShippingGroupOption');
 			$shipping_group_select = $shipping_group_option == 0 ? '0 AS SplitShippingGroup' : 'SplitShippingGroup';
 			if (count($split_shipments) > 1) {
 				$this->Application->SetVar('shipping_limitations_apply', 1);
 				// different shipping limitations apply
 				if ($limit_types == 'NONE') {
 					// order can't be shipped with single shipping type
 					$this->Application->SetVar('shipping_limitations_apply', 2);
 					$shipping_group_select = 'SplitShippingGroup';
 					$shipping_group_option = 1;
 				}
 			}
 			else {
 				$this->Application->SetVar('shipping_limitations_apply', 0);
 			}
 
 			$weight_sql = 'IF(oi.Weight IS NULL, 0, oi.Weight * oi.Quantity)';
 
 			$query = 'SELECT
 									'.$backorder_select.',
 									oi.ProductName,
 									oi.ShippingTypeId,
 									SUM(oi.Quantity) AS TotalItems,
 									SUM('.$weight_sql.') AS TotalWeight,
 									SUM(oi.Price * oi.Quantity) AS TotalAmount,'.
 									 //	calculate free Totals => SUM(ALL) - SUM(PROMO) 								'
 									'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.'
 								FROM '.$oi_table.' oi
 								LEFT JOIN '.$this->Application->getUnitOption('p', 'TableName').' p
 								ON oi.ProductId = p.ProductId
 								WHERE oi.OrderId = '.$ord_id.' AND p.Type = 1
 								GROUP BY BackOrderFlag, SplitShippingGroup
 								ORDER BY BackOrderFlag ASC, SplitShippingGroup ASC';
 			$shipments = $this->Conn->Query($query);
 
 			$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');
 
 			if(	($block_params['user_addr1'] == '' || $block_params['user_city'] == '' ||
 				$block_params['user_zip'] == '' || $block_params['user_country_id'] == '') &&
 				getArrayValue($params, 'invalid_address_render_as'))
 			{
 				$block_params['name'] = $params['invalid_address_render_as'];
 				return $this->Application->ParseBlock($block_params);
 			}
 
 			$group = 1;
 			foreach ($shipments as $shipment) {
 				$where = array('OrderId = '.$ord_id);
 				if ($shipping_group_option != 0) {
 					$where[] = 'SplitShippingGroup = '.$shipment['SplitShippingGroup'];
 				}
 				if ($shipping_option > 0) { // not all together
 					$where[] = 'BackOrderFlag = '.$shipment['BackOrderFlag'];
 				}
 
 				$query = 'UPDATE '.$oi_table.' SET PackageNum = '.$group.'
 									'.($where ? 'WHERE '.implode(' AND ', $where) : '');
 				$this->Conn->Query($query);
 				$group++;
 			}
 
 			$this->Application->RemoveVar('LastShippings');
 			$group = 1;
 			$this->Application->SetVar('ShipmentsExists', 1);
 			foreach ($shipments as $shipment) {
 
 
 				$block_params['package_num'] =  $group;
 
 				$block_params['limit_types'] = strpos($shipping_group_select, '0 AS') !== false ? $limit_types : $limitations_cache[$shipment['SplitShippingGroup']];
 
 				$this->Application->SetVar('ItemShipmentsExists', 1);
 
 				switch ($shipment['BackOrderFlag']) {
 					case 0:
 						if ( $this->CartHasBackOrders(Array()) && $shipping_option == 0 ) {
 							$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'];
 
 				$regional =& $this->Application->recallObject('lang.current');
 				if ($block_params['weight_metric'] == '')
 				{
 					$block_params['weight'] = $this->Application->Phrase('lu_NotAvailable');
 				}
 				elseif ($regional->GetDBField('UnitSystem') == 1)
 				{
 					$block_params['weight'] .= ' '.$this->Application->Phrase('lu_kg');
 				}
 				elseif ($regional->GetDBField('UnitSystem') == 2)
 				{
 					list($pounds, $ounces) = Kg2Pounds($block_params['weight']);
 					$block_params['weight'] = 	$pounds.' '.$this->Application->Phrase('lu_pounds').' '.
 												$ounces.' '.$this->Application->Phrase('lu_ounces');
 				}
 
 				$block_params['items'] = $shipment['TotalItems'];
 
 				$iso = $this->GetISO($params['currency']);
 				$amount = $this->ConvertCurrency($shipment['TotalAmount'], $iso);
 				$amount = sprintf("%.2f", $amount);
 
 //				$block_params['amount'] = $this->AddCurrencySymbol($amount, $iso);
 				$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;
 		}
 
 		function GetShippingLimitations($ord_id)
 		{
 			/*$query = 'SELECT
 										c.CachedShippingLimitation
 									FROM '.TABLE_PREFIX.'OrderItems AS oi
 									LEFT JOIN '.TABLE_PREFIX.'Products AS p
 									ON p.ProductId = oi.ProductId
 									LEFT JOIN '.TABLE_PREFIX.'CategoryItems AS ci
 									ON ci.ItemResourceId = p.ResourceId
 									LEFT JOIN '.TABLE_PREFIX.'Category AS c
 									ON c.CategoryId = ci.CategoryId
 									WHERE
 										oi.OrderId = '.$ord_id.'
 										AND
 										ci.PrimaryCat = 1
 										AND
 										c.CachedShippingMode = 1;';
 			$cat_limitations = $this->Conn->GetCol($query);*/
 			$cat_limitations = array();
 
 			$query = 'SELECT ShippingLimitation, ShippingMode, oi.ProductId as ProductId
 									FROM '.TABLE_PREFIX.'Products AS p
 									LEFT JOIN '.TABLE_PREFIX.'OrderItems AS oi ON
 									oi.ProductId = p.ProductId
 									WHERE oi.OrderId = '.$ord_id.' AND p.Type = 1'; // .' AND p.ShippingMode = 1';
 			$limitations = $this->Conn->Query($query, 'ProductId');
 
 			$split_shipments = array();
 
 			$limit = false;
 
 			$types_index = array();
 
 			// group products by shipping type range and caculate intersection of all types available for ALL products
 			// the intersaction caclulation is needed to determine if the order can be shipped with single type or not
 			if ($limitations) {
 				$limit_types = null;
 				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'] == 0) {
 						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 == null) { //it is null 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
 		 * @return bool
 		 */
 		function UsingCreditCard($params)
 		{
 			$object =& $this->getObject($params);
 
 			$pt = $object->GetDBField('PaymentType');
 
 			if (!$pt) {
 				$pt = $this->Conn->GetOne('SELECT PaymentTypeId FROM '.TABLE_PREFIX.'PaymentTypes WHERE IsPrimary = 1');
 				$object->SetDBField('PaymentType', $pt);
 			}
 
 			$pt_table = $this->Application->getUnitOption('pt','TableName');
 			$sql = 'SELECT GatewayId FROM %s WHERE PaymentTypeId = %s';
 			$gw_id = $this->Conn->GetOne( sprintf( $sql, $pt_table, $pt ) );
 
 			$sql = 'SELECT RequireCCFields FROM %s WHERE GatewayId = %s';
 
 			return $this->Conn->GetOne( sprintf($sql, TABLE_PREFIX.'Gateways', $gw_id) );
 		}
 
 		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;
 			}
 
 			$user =& $this->Application->recallObject('u', null, Array ('skip_autoload' => true));
 			/* @var $user kDBItem */
 
 			$order =& $this->Application->recallObject($this->Prefix . '.last');
 
 			$order_prefix = $params['type'] == 'billing' ? 'Billing' : 'Shipping';
 			$order_fields = Array (
 				'To', 'Company', 'Phone', 'Fax', 'Email', 'Address1',
 				'Address2', 'City', 'State', 'Zip', 'Country'
 			);
 
 			$names = explode(' ', $order->GetDBField($order_prefix.'To'), 2);
 			if (!$user->GetDBField('FirstName')) $user->SetDBField('FirstName', getArrayValue($names, 0) );
 			if (!$user->GetDBField('LastName')) $user->SetDBField('LastName', getArrayValue($names, 1) );
 			if (!$user->GetDBField('Company')) $user->SetDBField('Company', $order->GetDBField($order_prefix.'Company') );
 			if (!$user->GetDBField('Phone')) $user->SetDBField('Phone', $order->GetDBField($order_prefix.'Phone') );
 			if (!$user->GetDBField('Fax')) $user->SetDBField('Fax', $order->GetDBField($order_prefix.'Fax') );
 			if (!$user->GetDBField('Email')) $user->SetDBField('Email', $order->GetDBField($order_prefix.'Email') );
 			if (!$user->GetDBField('Street')) $user->SetDBField('Street', $order->GetDBField($order_prefix.'Address1') );
 			if (!$user->GetDBField('Street2')) $user->SetDBField('Street2', $order->GetDBField($order_prefix.'Address2') );
 			if (!$user->GetDBField('City')) $user->SetDBField('City', $order->GetDBField($order_prefix.'City') );
 			if (!$user->GetDBField('State')) $user->SetDBField('State', $order->GetDBField($order_prefix.'State') );
 			if (!$user->GetDBField('Zip')) $user->SetDBField('Zip', $order->GetDBField($order_prefix.'Zip') );
 			if (!$user->GetDBField('Country')) $user->SetDBField('Country', $order->GetDBField($order_prefix.'Country') );
 
 			$cs_helper =& $this->Application->recallObject('CountryStatesHelper');
 			/* @var $cs_helper kCountryStatesHelper */
 
 			$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(-1,-2, 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->GetVar('checkout_error') > 0;
 		}
 
 		function CheckoutError($params)
 		{
 			$error_codes = Array (
 				1 => 'state_changed',
 				2 => 'qty_unavailable',
 				3 => 'outofstock',
 				4 => 'invalid_code',
 				5 => 'code_expired',
 				6 => 'min_qty',
 				7 => 'code_removed',
 				8 => 'code_removed_automatically',
 				9 => 'changed_after_login',
 				10 => 'coupon_applied',
 				104 => 'invalid_gc_code',
 				105 => 'gc_code_expired',
 				107 => 'gc_code_removed',
 				108 => 'gc_code_removed_automatically',
 				110 => 'gift_certificate_applied',
 			);
 
 			$error_param = $error_codes[ $this->Application->GetVar('checkout_error') ];
 			return $this->Application->Phrase($params[$error_param]);
 		}
 
 		function GetFormAction($params)
 		{
 			$object =& $this->getObject($params);
 			/* @var $object OrdersItem */
 
 			$gw_data = $object->getGatewayData($params['payment_type_id']);
 
 			$this->Application->registerClass( $gw_data['ClassName'], GW_CLASS_PATH.'/'.$gw_data['ClassFile'] );
 			$gateway_object =& $this->Application->recallObject( $gw_data['ClassName'] );
 			/* @var $gateway_object kGWBase */
 
 			return $gateway_object->getFormAction($gw_data['gw_params']);
 		}
 
 		function GetFormHiddenFields($params)
 		{
 			$object =& $this->getObject($params);
 			/* @var $object OrdersItem */
 
 			$gw_data = $object->getGatewayData(array_key_exists('payment_type_id', $params) ? $params['payment_type_id'] : null);
 
 			$this->Application->registerClass( $gw_data['ClassName'], GW_CLASS_PATH.'/'.$gw_data['ClassFile'] );
 			$gateway_object =& $this->Application->recallObject( $gw_data['ClassName'] );
 
 			$tpl = '<input type="hidden" name="%s" value="%s" />'."\n";
 			$hidden_fields = $gateway_object->getHiddenFields($object->FieldValues, $params, $gw_data['gw_params']);
 
 			$ret = '';
 			if (!is_array($hidden_fields)) {
 				return $hidden_fields;
 			}
 			foreach($hidden_fields as $hidden_name => $hidden_value)
 			{
 				$ret .= sprintf($tpl, $hidden_name, $hidden_value);
 			}
 			return $ret;
 		}
 
 		function NeedsPlaceButton($params)
 		{
 			$object =& $this->getObject($params);
 			$gw_data = $object->getGatewayData();
 
 			$this->Application->registerClass( $gw_data['ClassName'], GW_CLASS_PATH.'/'.$gw_data['ClassFile'] );
 			$gateway_object =& $this->Application->recallObject( $gw_data['ClassName'] );
 
 			return $gateway_object->NeedPlaceButton($object->FieldValues, $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)
 		{
 			$object =& $this->getObject($params);
 			$shipping_info = unserialize( $object->GetDBField('ShippingInfo') );
 			if (count($shipping_info) > 1) {
 				return $this->Application->Phrase('lu_MultipleShippingTypes');
 			}
 			$shipping_info = array_shift($shipping_info);
 			return $shipping_info['ShippingName'];
 		}
 
 		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;
 		}
 
 		function OrderProductEmail($params)
 		{
 			$order =& $this->Application->recallObject('ord');
 			$orditems =& $this->Application->recallObject('orditems');
 
 			$sql = 'SELECT ResourceId
 					FROM '.TABLE_PREFIX.'Products
 					WHERE ProductId = '.$orditems->GetDBField('ProductId');
 			$resource_id = $this->Conn->GetOne($sql);
 
 			$ml_formatter =& $this->Application->recallObject('kMultiLanguage');
 			$custom_fields = $this->Application->getUnitOption('p', 'CustomFields');
 			$custom_name = $ml_formatter->LangFieldName('cust_'.array_search($params['msg_custom_field'], $custom_fields));
 
 			$sql = 'SELECT '.$custom_name.'
 					FROM '.$this->Application->getUnitOption('p-cdata', 'TableName').'
 					WHERE ResourceId = '.$resource_id;
 			$message_template = $this->Conn->GetOne($sql);
 
 			if (!$message_template || trim($message_template) == '') {
 				// message template missing
 				return ;
 			}
 
 			$from_name = strip_tags($this->Application->ConfigValue('Site_Name'));
 			$from_email = $this->Application->ConfigValue('Smtp_AdminMailFrom');
 
 			$to_name = $order->GetDBField('BillingTo');
 			$to_email = $order->GetDBField('BillingEmail');
 			if (!$to_email) {
 				// billing email is empty, then use user's email
 				$sql = 'SELECT Email
 						FROM '.$this->Application->getUnitOption('u', 'TableName').'
 						WHERE PortalUserId = '.$order->GetDBField('PortalUserId');
 				$to_email = $this->Conn->GetOne($sql);
 			}
 
 			$esender =& $application->recallObject('EmailSender.-product');
 			/* @var $esender kEmailSendingHelper */
 
 			$esender->SetFrom($from_email, $from_name);
 			$esender->AddTo($to_email, $to_name);
 
 			$email_events_eh =& $this->Application->recallObject('emailevents_EventHandler');
 			/* @var $email_events_eh EmailEventsEventsHandler */
 
 			list ($message_headers, $message_body) = $email_events_eh->ParseMessageBody($message_template, Array());
 			if (!trim($message_body)) {
 				// message body missing
 				return false;
 			}
 
 			foreach ($message_headers as $header_name => $header_value) {
 				$esender->SetEncodedHeader($header_name, $header_value);
 			}
 
 			$esender->SetHTML($message_body);
 			$esender->Deliver();
 		}
 
 		function PrintTotals($params)
 		{
 			$order =& $this->getObject($params);
 
 			$totals = array();
 			if (ABS($order->GetDBField('SubTotal') - $order->GetDBField('AmountWithoutVAT')) > 0.01) {
 				$totals[] = 'products';
 			}
 
 			$has_tangible = $this->OrderHasTangibleItems($params);
 
 			if ($has_tangible && $order->GetDBField('ShippingTaxable')) {
 				$totals[] = 'shipping';
 			}
 
 			if ($order->GetDBField('ProcessingFee') > 0 && $order->GetDBField('ProcessingTaxable')) {
 				$totals[] = 'processing';
 			}
 
 			if ($order->GetDBField('ReturnTotal') > 0 && $order->GetDBField('ReturnTotal')) {
 				$totals[] = 'return';
 			}
 
 			$totals[] = 'sub_total';
 
 			if ($order->GetDBField('VAT') > 0) {
 				$totals[] = 'vat';
 			}
 
 			if ($has_tangible && !$order->GetDBField('ShippingTaxable')) {
 				$totals[] = 'shipping';
 			}
 
 			if ($order->GetDBField('ProcessingFee') > 0 && !$order->GetDBField('ProcessingTaxable')) {
 				$totals[] = 'processing';
 			}
 
 
 			$o = '';
 			foreach ($totals as $type)
 			{
 				if ($element = getArrayValue($params, $type.'_render_as')) {
 					$o .= $this->Application->ParseBlock( array('name' => $element), 1 );
 				}
 			}
 			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->FieldValues, $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']);
 
 			$this->Application->registerClass( $gw_data['ClassName'], GW_CLASS_PATH.'/'.$gw_data['ClassFile'] );
 			$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->FieldValues;
 			$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->RecordsCount < $max;
 		}
 
 		function FreePromoShippingAvailable($params)
 		{
 			$object =& $this->Application->recallObject('orditems');
 			$free_ship = $object->GetDBField('MinQtyFreePromoShipping');
 			$tangible = ($object->GetDBField('Type') == 1)? 1 : 0;
 			return ($tangible && ($free_ship > 0 && $free_ship <= $object->GetDBField('Quantity')))? 1 : 0;
 		}
 
 		/**
 		 * Creates link for removing coupon or gift certificate
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function RemoveCouponLink($params)
 		{
 			$type = strtolower($params['type']);
 			$url_params = Array (
 				'pass' => 'm,ord',
 				'ord_event' => ($type == 'coupon') ? 'OnRemoveCoupon' : 'OnRemoveGiftCertificate',
 				'm_cat_id' => 0,
 			);
 
 			return $this->Application->HREF('', '', $url_params);
 		}
 
 		/**
 		 * Calculates total weight of items in shopping cart
 		 *
 		 * @param Array $params
 		 * @return float
 		 */
 		function TotalOrderWeight($params)
 		{
 			$object =& $this->getObject();
 			/* @var $object kDBItem */
 
 			$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');
 			}
 
 			$regional =& $this->Application->recallObject('lang.current');
 
 			switch ($regional->GetDBField('UnitSystem')) {
 				case 1:
 					// metric system -> add kg sign
 					$total_weight .= ' '.$this->Application->Phrase('lu_kg');
 					break;
 
 				case 2:
 					// uk system -> convert to pounds
 					list($pounds, $ounces) = Kg2Pounds($total_weight);
 					$total_weight = $pounds.' '.$this->Application->Phrase('lu_pounds').' '.$ounces.' '.$this->Application->Phrase('lu_ounces');
 					break;
 			}
 
 			return $total_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 '';
 			}
 
 			$object =& $this->getObject($params);
 			/* @var $object OrdersItem */
 
 			$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)
 		{
 			$object =& $this->getObject($params);
 			/* @var $object kDBItem */
 
 			$full_path = USPS_LABEL_FOLDER . $object->GetDBField( $params['field'] ) . '.pdf';
 
 			return file_exists($full_path) && is_file($full_path);
 		}
 
 		/**
 		 * Prints USPS errors from session
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function PrintUSPSErrors($params)
 		{
 			$o = '';
 			$ses_usps_erros = Array();
 			$ses_usps_erros = unserialize($this->Application->RecallVar('usps_errors'));
 			if ( count($ses_usps_erros) > 0 && is_array($ses_usps_erros)) {
 				foreach ( $ses_usps_erros as $order_number => $error_description ) {
 					$block_params = Array();
 					$block_params['name'] = $params['render_as'];
 					$block_params['order_number'] = $order_number;
 					$block_params['error_description'] = $error_description;
 					$o.=$this->Application->ParseBlock($block_params, 1);
 				}
 				$this->Application->RemoveVar('usps_errors');
 			}
 
 			return $o;
 		}
 
 	}
\ No newline at end of file