<?php
/**
* @version	$Id: products_event_handler.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 ProductsEventHandler extends kCatDBEventHandler {

	/**
	 * Allows to override standard permission mapping
	 *
	 * @return void
	 * @access protected
	 * @see kEventHandler::$permMapping
	 */
	protected function mapPermissions()
	{
		parent::mapPermissions();

		$permissions = Array(
			// front
			'OnCancelAction'		=>	Array('self' => true),
			'OnRateProduct'			=>	Array('self' => true),
			'OnClearRecent'			=>	Array('self' => true),
			'OnRecommendProduct'	=>	Array('self' => true),
			'OnAddToCompare'		=>	Array('self' => true),
			'OnRemoveFromCompare'	=>	Array('self' => true),
			'OnCancelCompare'		=>	Array('self' => true),

			// admin
			'OnQtyAdd'			=>	Array('self' => 'add|edit'),
			'OnQtyRemove'		=>	Array('self' => 'add|edit'),
			'OnQtyOrder'		=>	Array('self' => 'add|edit'),
			'OnQtyReceiveOrder'	=>	Array('self' => 'add|edit'),
			'OnQtyCancelOrder'	=>	Array('self' => 'add|edit'),
		);

		$this->permMapping = array_merge($this->permMapping, $permissions);
	}

	/**
	 * Define alternative event processing method names
	 *
	 * @return void
	 * @see kEventHandler::$eventMethods
	 * @access protected
	 */
	protected function mapEvents()
	{
		parent::mapEvents();	// ensure auto-adding of approve/decine and so on events

		$product_events = Array (
			'OnQtyAdd'=>'InventoryAction',
			'OnQtyRemove'=>'InventoryAction',
			'OnQtyOrder'=>'InventoryAction',
			'OnQtyReceiveOrder'=>'InventoryAction',
			'OnQtyCancelOrder'=>'InventoryAction',
		);

		$this->eventMethods = array_merge($this->eventMethods, $product_events);
	}

	/**
	 * Sets default processing data for subscriptions
	 *
	 * @param kEvent $event
	 * @return void
	 * @access protected
	 */
	protected function OnBeforeItemCreate(kEvent $event)
	{
		parent::OnBeforeItemCreate($event);

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

		$product_approve_events = Array (
			2 => 'p:OnSubscriptionApprove',
			4 => 'p:OnDownloadableApprove',
			5 => 'p:OnPackageApprove'
		);

		$product_type = $object->GetDBField('Type');

		$type_found = in_array($product_type, array_keys($product_approve_events));

		if ( $type_found && !$object->GetDBField('ProcessingData') ) {
			$processing_data = Array ('ApproveEvent' => $product_approve_events[$product_type]);
			$object->SetDBField('ProcessingData', serialize($processing_data));
		}
	}

	/**
	 * Process product count manipulations
	 *
	 * @param kEvent $event
	 * @access private
	 */
	function InventoryAction($event)
	{
		/** @var kDBItem $object */
		$object = $event->getObject();

		$field_values = $this->getSubmittedFields($event);
		$object->SetFieldsFromHash($field_values);
		$event->setEventParam('form_data', $field_values);

		if ($object->GetDBField('InventoryStatus') == 2) {
			// inventory by options (use first selected combination in grid)
			$combinations = $this->Application->GetVar('poc_grid');
			list ($combination_id, ) = each($combinations);
		}
		else {
			// inventory by product
			$combination_id = 0;
		}

		// save id of selected option combination & preselect it in grid
		$this->Application->SetVar('combination_id', $combination_id);

		$this->ScheduleInventoryAction($event->Name, $object->GetId(), $object->GetDBField('Qty'), $combination_id);

		$object->Validate();

		if ( !$object->GetErrorPseudo('Qty') ){
			// only update, when no error on that field
			$this->modifyInventory($event->Name, $object, $object->GetDBField('Qty'), $combination_id);
		}

		$object->SetDBField('Qty', null);
		$event->redirect = false;
	}

	/**
	 * Perform inventory action on supplied object
	 *
	 * @param string $action event name which is actually called by user
	 * @param ProductsItem $product
	 * @param int $qty
	 * @param int $combination_id
	 */
	function modifyInventory($action, &$product, $qty, $combination_id)
	{
		if ($product->GetDBField('InventoryStatus') == 2) {
			// save inventory changes to option combination instead of product
			$object = $this->Application->recallObject('poc.-item', null, Array('skip_autoload' => true));
			$object->Load($combination_id);
		}
		elseif ($combination_id > 0) {
			// combination id present, but not inventory by combinations => skip
			return false;
		}
		elseif ($product->GetDBField('InventoryStatus') == 1) {
			// save inventory changes to product
			$object =& $product;
		}
		else {
			// product has inventory actions, but don't use inventory => skip
			return false;
		}

		if (!$object->isLoaded()) {
			// product/combination in action doesn't exist in database by now
			return false;
		}

		switch ($action) {
			case 'OnQtyAdd':
				$object->SetDBField('QtyInStock', $object->GetDBField('QtyInStock') + $qty);
				break;

			case 'OnQtyRemove':
				if ($object->GetDBField('QtyInStock') < $qty) {
					$qty = $object->GetDBField('QtyInStock');
				}
				$object->SetDBField('QtyInStock', $object->GetDBField('QtyInStock') - $qty);
				break;

			case 'OnQtyOrder':
				$object->SetDBField('QtyOnOrder', $object->GetDBField('QtyOnOrder') + $qty);
				break;

			case 'OnQtyReceiveOrder':
				$object->SetDBField('QtyOnOrder', $object->GetDBField('QtyOnOrder') - $qty);
				$object->SetDBField('QtyInStock', $object->GetDBField('QtyInStock') + $qty);
				break;

			case 'OnQtyCancelOrder':
				$object->SetDBField('QtyOnOrder', $object->GetDBField('QtyOnOrder') - $qty);
				break;
		}

		return $object->Update();
	}

	function ScheduleInventoryAction($action, $prod_id, $qty, $combination_id = 0)
	{
		$inv_actions = $this->Application->RecallVar('inventory_actions');
		if (!$inv_actions) {
			$inv_actions = Array();
		}
		else {
			$inv_actions = unserialize($inv_actions);
		}

		array_push($inv_actions, Array('action' => $action, 'product_id' => $prod_id, 'combination_id' => $combination_id, 'qty' => $qty));

		$this->Application->StoreVar('inventory_actions', serialize($inv_actions));
	}

	function RealInventoryAction($action, $prod_id, $qty, $combination_id)
	{
		$product = $this->Application->recallObject('p.liveitem', null, Array('skip_autoload' => true));
		$product->SwitchToLive();
		$product->Load($prod_id);

		$this->modifyInventory($action, $product, $qty, $combination_id);
	}

	function RunScheduledInventoryActions($event)
	{
		$inv_actions = $this->Application->GetVar('inventory_actions');
		if (!$inv_actions) {
			return;
		}
		$inv_actions = unserialize($inv_actions);

		$products = array();
		foreach($inv_actions as $an_action) {
			$this->RealInventoryAction($an_action['action'], $an_action['product_id'], $an_action['qty'], $an_action['combination_id']);
			array_push($products, $an_action['product_id'].'_'.$an_action['combination_id']);
		}

		$products = array_unique($products);
		if ($products) {
			$product_obj = $this->Application->recallObject('p.liveitem', null, Array('skip_autoload' => true));
			$product_obj->SwitchToLive();
			foreach ($products as $product_key) {
				list($prod_id, $combination_id) = explode('_', $product_key);
			$product_obj->Load($prod_id);
				$this->FullfillBackOrders($product_obj, $combination_id);
			}
		}
	}

	/**
	 * In case if products arrived into inventory and they are required by old (non processed) orders, then use them (products) in that orders
	 *
	 * @param ProductsItem $product
	 * @param int $combination_id
	 */
	function FullfillBackOrders(&$product, $combination_id)
	{
		if ( !$this->Application->ConfigValue('Comm_Process_Backorders_Auto') ) return;

		if ($combination_id && ($product->GetDBField('InventoryStatus') == 2)) {
			// if combination id present and inventory by combinations
			$poc_idfield = $this->Application->getUnitOption('poc', 'IDField');
			$poc_tablename = $this->Application->getUnitOption('poc', 'TableName');
			$sql = 'SELECT QtyInStock
					FROM '.$poc_tablename.'
					WHERE '.$poc_idfield.' = '.$combination_id;
			$stock_qty = $this->Conn->GetOne($sql);
		}
		else {
			// inventory by product
			$stock_qty = $product->GetDBField('QtyInStock');
		}

		$qty = (int) $stock_qty - $product->GetDBField('QtyInStockMin');
		$prod_id = $product->GetID();
		if ($prod_id <= 0 || !$prod_id || $qty <= 0) return;

		//selecting up to $qty backorders with $prod_id where full qty is not reserved
		$query = 'SELECT '.TABLE_PREFIX.'Orders.OrderId
							FROM '.TABLE_PREFIX.'OrderItems
					LEFT JOIN '.TABLE_PREFIX.'Orders ON '.TABLE_PREFIX.'Orders.OrderId = '.TABLE_PREFIX.'OrderItems.OrderId
					WHERE (ProductId = '.$prod_id.') AND (Quantity > QuantityReserved) AND (Status = '.ORDER_STATUS_BACKORDERS.')
							GROUP BY '.TABLE_PREFIX.'Orders.OrderId
							ORDER BY OrderDate ASC
							LIMIT 0,'.$qty; //assuming 1 item per order - minimum possible

		$orders = $this->Conn->GetCol($query);

		if ( !$orders ) {
			return;
		}

		/** @var OrdersItem $order */
		$order = $this->Application->recallObject('ord.-inv', null, array('skip_autoload' => true));

		foreach ($orders as $ord_id) {
			$order->Load($ord_id);

			$this->Application->emailAdmin('BACKORDER.FULLFILL');

			// Reserve what's possible in any case.
			$reserve_event = new kEvent('ord:OnReserveItems');
			$this->Application->HandleEvent($reserve_event);

			// In case the order is ready to process - process it.
			if ( $reserve_event->status == kEvent::erSUCCESS ) {
				$this->Application->HandleEvent(new kEvent('ord:OnOrderProcess'));
			}
		}
	}

	/**
	 * Occurs before an item is deleted from live table when copying from temp
	 * (temp handler deleted all items from live and then copy over all items from temp)
	 * Id of item being deleted is passed as event' 'id' param
	 *
	 * @param kEvent $event
	 * @return void
	 * @access protected
	 */
	protected function OnBeforeDeleteFromLive(kEvent $event)
	{
		parent::OnBeforeDeleteFromLive($event);

		/** @var kCatDBItem $product */
		$product = $this->Application->recallObject($event->Prefix . '.itemlive', null, Array ('skip_autoload' => true));

		$product->SwitchToLive();
		$id = $event->getEventParam('id');

		if ( !$product->Load($id) ) {
			// this will make sure New product will not be overwritten with empty data
			return ;
		}

		/** @var kCatDBItem $temp */
		$temp = $this->Application->recallObject($event->Prefix . '.itemtemp', null, Array ('skip_autoload' => true));

		$temp->SwitchToTemp();
		$temp->Load($id);

		$temp->SetDBFieldsFromHash($product->GetFieldValues(), Array ('QtyInStock', 'QtyReserved', 'QtyBackOrdered', 'QtyOnOrder'));
		$temp->Update();
	}

	/**
	 * Removes any information about current/selected ids
	 * from Application variables and Session
	 *
	 * @param kEvent $event
	 * @return void
	 * @access protected
	 */
	protected function clearSelectedIDs(kEvent $event)
	{
		parent::clearSelectedIDs($event);

		$this->Application->SetVar('inventory_actions', $this->Application->RecallVar('inventory_actions'));
		$this->Application->RemoveVar('inventory_actions');
	}

	/**
	 * Saves content of temp table into live and
	 * redirects to event' default redirect (normally grid template)
	 *
	 * @param kEvent $event
	 * @return void
	 * @access protected
	 */
	protected function OnSave(kEvent $event)
	{
		parent::OnSave($event);

		if ( $event->status == kEvent::erSUCCESS ) {
			$this->RunScheduledInventoryActions($event);
		}
	}

	/**
	 * Prepare temp tables for creating new item
	 * but does not create it. Actual create is
	 * done in OnPreSaveCreated
	 *
	 * @param kEvent $event
	 * @return void
	 * @access protected
	 */
	protected function OnPreCreate(kEvent $event)
	{
		parent::onPreCreate($event);

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

		$object->SetDBField('Type', $this->Application->GetVar($event->getPrefixSpecial(true) . '_new_type'));
	}

	/**
	 * Saves edited item in temp table and loads
	 * item with passed id in current template
	 * Used in Prev/Next buttons
	 *
	 * @param kEvent $event
	 * @return void
	 * @access protected
	 */
	protected function OnPreSaveAndGo(kEvent $event)
	{
		$event->CallSubEvent('OnPreSave');
		$this->LoadItem($event);

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

		$from_type = $object->GetDBField('Type');
		if ( $event->status == kEvent::erSUCCESS ) {
			$this->Application->SetVar($event->getPrefixSpecial() . '_id', $this->Application->GetVar($event->getPrefixSpecial(true) . '_GoId'));
			$this->LoadItem($event);
			$to_type = $object->GetDBField('Type');

			if ( $from_type != $to_type ) {
				$from_tabs = $this->GetTabs($from_type);
				$from_tab_i = array_search($this->Application->GetVar('t'), $from_tabs);

				$to_tabs = $this->GetTabs($to_type);
				$to_tab = $this->Application->GetVar('t');

				$found = false;
				while (!isset($to_tabs[$from_tab_i]) && $from_tab_i < count($to_tabs)) {
					$from_tab_i++;
				}

				if ( !isset($to_tabs[$from_tab_i]) ) {
					$from_tab_i = 0;
				}

				$to_tab = $to_tabs[$from_tab_i];

				$event->redirect = $to_tab;
			}
		}
	}

	function GetTabs($type)
	{
		switch($type)
		{
			case 1:
				return Array(
					0 => 'in-commerce/products/products_edit',
					1 => 'in-commerce/products/products_inventory',
					2 => 'in-commerce/products/products_pricing',
					3 => 'in-commerce/products/products_categories',
					4 => 'in-commerce/products/products_images',
					5 => 'in-commerce/products/products_reviews',
					6 => 'in-commerce/products/products_custom',
				);

			case 2:
				return Array(
					0 => 'in-commerce/products/products_edit',
					1 => 'in-commerce/products/products_access',
					/*2 => 'in-commerce/products/products_access_pricing',*/
					3 => 'in-commerce/products/products_categories',
					4 => 'in-commerce/products/products_images',
					5 => 'in-commerce/products/products_reviews',
					6 => 'in-commerce/products/products_custom',
				);

			case 3:
				return Array(
					0 => 'in-commerce/products/products_edit',

					2 => 'in-commerce/products/products_access_pricing',
					3 => 'in-commerce/products/products_categories',
					4 => 'in-commerce/products/products_images',
					5 => 'in-commerce/products/products_reviews',
					6 => 'in-commerce/products/products_custom',
				);

			case 4:
				return Array(
					0 => 'in-commerce/products/products_edit',

					2 => 'in-commerce/products/products_files',
					3 => 'in-commerce/products/products_categories',
					4 => 'in-commerce/products/products_images',
					5 => 'in-commerce/products/products_reviews',
					6 => 'in-commerce/products/products_custom',
				);
		}
	}

	/**
	 * Return type clauses for list bulding on front
	 *
	 * @param kEvent $event
	 * @return Array
	 */
	function getTypeClauses($event)
	{
		$types = $event->getEventParam('types');
		$types = $types ? explode(',', $types) : Array ();

		$except_types = $event->getEventParam('except');
		$except_types = $except_types ? explode(',', $except_types) : Array ();

		/** @var kDBList $object */
		$object = $event->getObject();

		$type_clauses = parent::getTypeClauses($event);

		$type_clauses['featured']['include'] = '%1$s.Featured = 1 AND ' . TABLE_PREFIX . 'CategoryItems.PrimaryCat = 1';
		$type_clauses['featured']['except'] = '%1$s.Featured != 1 AND ' . TABLE_PREFIX . 'CategoryItems.PrimaryCat = 1';
		$type_clauses['featured']['having_filter'] = false;

		$type_clauses['onsale']['include'] = '%1$s.OnSale = 1 AND ' . TABLE_PREFIX . 'CategoryItems.PrimaryCat = 1';
		$type_clauses['onsale']['except'] = '%1$s.OnSale != 1 AND ' . TABLE_PREFIX . 'CategoryItems.PrimaryCat = 1';
		$type_clauses['onsale']['having_filter'] = false;

		// products from selected manufacturer: begin
		$manufacturer = $event->getEventParam('manufacturer');
		if ( !$manufacturer ) {
			$manufacturer = $this->Application->GetVar('manuf_id');
		}

		if ( $manufacturer ) {
			$type_clauses['manufacturer']['include'] = '%1$s.ManufacturerId = ' . $manufacturer . ' AND PrimaryCat = 1';
			$type_clauses['manufacturer']['except'] = '%1$s.ManufacturerId != ' . $manufacturer . ' AND PrimaryCat = 1';
			$type_clauses['manufacturer']['having_filter'] = false;
		}
		// products from selected manufacturer: end

		// recent products: begin
		$recent = $this->Application->RecallVar('recent_products');
		if ( $recent ) {
			$recent = unserialize($recent);
			$type_clauses['recent']['include'] = '%1$s.ProductId IN (' . implode(',', $recent) . ') AND PrimaryCat = 1';
			$type_clauses['recent']['except'] = '%1$s.ProductId NOT IN (' . implode(',', $recent) . ') AND PrimaryCat = 1';
		}
		else {
			$type_clauses['recent']['include'] = '0';
			$type_clauses['recent']['except'] = '1';
		}
		$type_clauses['recent']['having_filter'] = false;
		// recent products: end

		// compare products: begin
		if ( in_array('compare', $types) || in_array('compare', $except_types) ) {
			$compare_products = $this->getCompareProducts();

			if ( $compare_products ) {
				$compare_products = $this->Conn->qstrArray($compare_products);
				$type_clauses['compare']['include'] = '%1$s.ProductId IN (' . implode(',', $compare_products) . ') AND PrimaryCat = 1';
				$type_clauses['compare']['except'] = '%1$s.ProductId NOT IN (' . implode(',', $compare_products) . ') AND PrimaryCat = 1';
			}
			else {
				$type_clauses['compare']['include'] = '0';
				$type_clauses['compare']['except'] = '1';
			}

			$type_clauses['compare']['having_filter'] = false;

			if ( $event->getEventParam('per_page') === false ) {
				$event->setEventParam('per_page', $this->Application->ConfigValue('MaxCompareProducts'));
			}
		}
		// compare products: end

		// products already in shopping cart: begin
		if ( in_array('in_cart', $types) || in_array('in_cart', $except_types) ) {
			$order_id = $this->Application->RecallVar('ord_id');

			if ( $order_id ) {
				$sql = 'SELECT ProductId
						FROM ' . TABLE_PREFIX . 'OrderItems
						WHERE OrderId = ' . $order_id;
				$in_cart = $this->Conn->GetCol($sql);

				if ( $in_cart ) {
					$type_clauses['in_cart']['include'] = '%1$s.ProductId IN (' . implode(',', $in_cart) . ') AND PrimaryCat = 1';
					$type_clauses['in_cart']['except'] = '%1$s.ProductId NOT IN (' . implode(',', $in_cart) . ') AND PrimaryCat = 1';
				}
				else {
					$type_clauses['in_cart']['include'] = '0';
					$type_clauses['in_cart']['except'] = '1';
				}
			}
			else {
				$type_clauses['in_cart']['include'] = '0';
				$type_clauses['in_cart']['except'] = '1';
			}

			$type_clauses['in_cart']['having_filter'] = false;
		}
		// products already in shopping cart: end

		// my downloadable products: begin
		if ( in_array('my_downloads', $types) || in_array('my_downloads', $except_types) ) {
			$user_id = $this->Application->RecallVar('user_id');

			$sql = 'SELECT ProductId
					FROM ' . TABLE_PREFIX . 'UserFileAccess
					WHERE PortalUserId = ' . $user_id;
			$my_downloads = $user_id > 0 ? $this->Conn->GetCol($sql) : false;

			if ( $my_downloads ) {
				$type_clauses['my_downloads']['include'] = '%1$s.ProductId IN (' . implode(',', $my_downloads) . ') AND PrimaryCat = 1';
				$type_clauses['my_downloads']['except'] = '%1$s.ProductId NOT IN (' . implode(',', $my_downloads) . ') AND PrimaryCat = 1';
			}
			else {
				$type_clauses['my_downloads']['include'] = '0';
				$type_clauses['my_downloads']['except'] = '1';
			}

			$type_clauses['my_downloads']['having_filter'] = false;
		}
		// my downloadable products: end

		// my favorite products: begin
		if ( in_array('wish_list', $types) || in_array('wish_list', $except_types) ) {
			$sql = 'SELECT ResourceId
					FROM ' . $this->Application->getUnitOption('fav', 'TableName') . '
					WHERE PortalUserId = ' . (int)$this->Application->RecallVar('user_id');
			$wishlist_ids = $this->Conn->GetCol($sql);

			if ( $wishlist_ids ) {
				$type_clauses['wish_list']['include'] = '%1$s.ResourceId IN (' . implode(',', $wishlist_ids) . ') AND PrimaryCat = 1';
				$type_clauses['wish_list']['except'] = '%1$s.ResourceId NOT IN (' . implode(',', $wishlist_ids) . ') AND PrimaryCat = 1';
			}
			else {
				$type_clauses['wish_list']['include'] = '0';
				$type_clauses['wish_list']['except'] = '1';
			}

			$type_clauses['wish_list']['having_filter'] = false;
		}
		// my favorite products: end

		// products from package: begin
		if ( in_array('content', $types) || in_array('content', $except_types) ) {
			$object->removeFilter('category_filter');
			$object->AddGroupByField('%1$s.ProductId');

			/** @var ProductsItem $object_product */
			$object_product = $this->Application->recallObject($event->Prefix);

			$content_ids_array = $object_product->GetPackageContentIds();

			if ( sizeof($content_ids_array) == 0 ) {
				$content_ids_array = array ('-1');
			}

			if ( sizeof($content_ids_array) > 0 ) {
				$type_clauses['content']['include'] = '%1$s.ProductId IN (' . implode(',', $content_ids_array) . ')';
			}
			else {
				$type_clauses['content']['include'] = '0';
			}

			$type_clauses['related']['having_filter'] = false;
		}
		// products from package: end

		$object->addFilter('not_virtual', '%1$s.Virtual = 0');

		if ( !$this->Application->isAdminUser ) {
			$object->addFilter('expire_filter', '%1$s.Expire IS NULL OR %1$s.Expire > ' . adodb_mktime());
		}

		return $type_clauses;
	}

	function OnClearRecent($event)
	{
		$this->Application->RemoveVar('recent_products');
	}

	/**
	 * Occurs, when user rates a product
	 *
	 * @param kEvent $event
	 */
	function OnRateProduct($event)
	{
		$event->SetRedirectParam('pass', 'all,p');
		$event->redirect = $this->Application->GetVar('success_template');

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

		$user_id = $this->Application->RecallVar('user_id');

		$sql = '	SELECT * FROM ' . TABLE_PREFIX . 'SpamControl
					WHERE ItemResourceId=' . $object->GetDBField('ResourceId') . '
					AND IPaddress="' . $this->Application->getClientIp() . '"
					AND PortalUserId=' . $user_id . '
					AND DataType="Rating"';
		$res = $this->Conn->GetRow($sql);

		if ( $res && $res['Expire'] < adodb_mktime() ) {
			$sql = '	DELETE FROM ' . TABLE_PREFIX . 'SpamControl
						WHERE ItemResourceId=' . $object->GetDBField('ResourceId') . '
						AND IPaddress="' . $this->Application->getClientIp() . '"
						AND PortalUserId=' . $user_id . '
						AND DataType="Rating"';
			$this->Conn->Query($sql);
			unset($res);
		}

		$new_rating = $this->Application->GetVar('rating');

		if ( $new_rating !== false && !$res ) {
			$rating = $object->GetDBField('CachedRating');
			$votes = $object->GetDBField('CachedVotesQty');
			$new_votes = $votes + 1;

			$rating = (($rating * $votes) + $new_rating) / $new_votes;
			$object->SetDBField('CachedRating', $rating);
			$object->SetDBField('CachedVotesQty', $new_votes);
			$object->Update();

			$expire = adodb_mktime() + $this->Application->ConfigValue('product_ReviewDelay_Value') * $this->Application->ConfigValue('product_ReviewDelay_Interval');
			$sql = '	INSERT INTO ' . TABLE_PREFIX . 'SpamControl
							(ItemResourceId, IPaddress, PortalUserId, DataType, Expire)
						VALUES (' . $object->GetDBField('ResourceId') . ',
								"' . $this->Application->getClientIp() . '",
								' . $user_id . ',
								"Rating",
								' . $expire . ')';
			$this->Conn->Query($sql);
		}
		else {
			$event->status == kEvent::erFAIL;
			$event->redirect = false;
			$object->SetError('CachedRating', 'too_frequent', 'lu_ferror_rate_duplicate');
		}
	}

	/**
	 * Enter description here...
	 *
	 * @param kEvent $event
	 */
	function OnCancelAction($event)
	{
		$event->SetRedirectParam('pass', 'all,p');
		$event->redirect = $this->Application->GetVar('cancel_template');
	}

	/**
	 * Enter description here...
	 *
	 * @param kEvent $event
	 */
	function OnRecommendProduct($event)
	{
		// used for error reporting only -> rewrite code + theme (by Alex)
		$object = $this->Application->recallObject('u', null, Array('skip_autoload' => true)); // TODO: change theme too
		/** @var kDBItem $object */

		$friend_email = $this->Application->GetVar('friend_email');
		$friend_name = $this->Application->GetVar('friend_name');
		$my_email = $this->Application->GetVar('your_email');
		$my_name = $this->Application->GetVar('your_name');
		$my_message = $this->Application->GetVar('your_message');

		$send_params = array();
		$send_params['to_email']=$friend_email;
		$send_params['to_name']=$friend_name;
		$send_params['from_email']=$my_email;
		$send_params['from_name']=$my_name;
		$send_params['message']=$my_message;

		if ( preg_match('/' . REGEX_EMAIL_USER . '@' . REGEX_EMAIL_DOMAIN . '/', $friend_email) ) {
			$user_id = $this->Application->RecallVar('user_id');
			$email_sent = $this->Application->emailUser('PRODUCT.SUGGEST', $user_id, $send_params);
			$this->Application->emailAdmin('PRODUCT.SUGGEST');

			if ( $email_sent ) {
				$event->setRedirectParams(Array ('opener' => 's', 'pass' => 'all'));
				$event->redirect = $this->Application->GetVar('template_success');
			}
			else {
//				$event->setRedirectParams(Array('opener' => 's', 'pass' => 'all'));
//				$event->redirect = $this->Application->GetVar('template_fail');

				$object->SetError('Email', 'send_error', 'lu_email_send_error');
				$event->status = kEvent::erFAIL;
			}
		}
		else {
			$object->SetError('Email', 'invalid_email', 'lu_InvalidEmail');
			$event->status = kEvent::erFAIL;
		}
	}

	/**
	 * Creates/updates virtual product based on listing type data
	 *
	 * @param kEvent $event
	 */
	function OnSaveVirtualProduct($event)
	{
		$object = $event->getObject( Array('skip_autoload' => true) );
		$listing_type = $this->Application->recallObject('lst', null, Array('skip_autoload' => true));
		$listing_type->Load($event->MasterEvent->getEventParam('id'));

		$product_id = $listing_type->GetDBField('VirtualProductId');

		if ($product_id) {
			$object->Load($product_id);
		}

		if (!$listing_type->GetDBField('EnableBuying')) {
			if ($product_id) {
				// delete virtual product here
				$temp_handler = $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
				$temp_handler->DeleteItems($event->Prefix, $event->Special, Array($product_id));

				$listing_type->SetDBField('VirtualProductId', 0);
				$listing_type->Update();
			}
			return true;
		}

		$ml_formatter = $this->Application->recallObject('kMultiLanguage');
		$object->SetDBField($ml_formatter->LangFieldName('Name'), $listing_type->GetDBField('ShopCartName') );
		$object->SetDBField($ml_formatter->LangFieldName('Description'), $listing_type->GetDBField('Description'));
		$object->SetDBField('SKU', 'ENHANCE_LINK_'.abs( crc32( $listing_type->GetDBField('Name') ) ) );

		if ($product_id) {
			$object->Update();
		}
		else {
			$object->SetDBField('Type', 2);
			$object->SetDBField('Status', 1);
			$object->SetDBField('HotItem', 0);
			$object->SetDBField('PopItem', 0);
			$object->SetDBField('NewItem', 0);
			$object->SetDBField('Virtual', 1);

//			$processing_data = Array('ApproveEvent' => 'ls:EnhanceLinkAfterOrderApprove', 'ExpireEvent' => 'ls:ExpireLink');
			$processing_data = Array(	'ApproveEvent'			=>	'ls:EnhanceLinkAfterOrderApprove',
										'DenyEvent'				=>	'ls:EnhanceLinkAfterOrderDeny',
										'CompleteOrderEvent'	=>	'ls:EnhancedLinkOnCompleteOrder',
										'ExpireEvent'			=>	'ls:ExpireLink',
										'HasNewProcessing'		=>	1);
			$object->SetDBField('ProcessingData', serialize($processing_data));
			$object->Create();

			$listing_type->SetDBField('VirtualProductId', $object->GetID());
			$listing_type->Update();
		}

		$additiona_fields = Array(	'AccessDuration'	=>	$listing_type->GetDBField('Duration'),
									'AccessUnit'		=>	$listing_type->GetDBField('DurationType'),
							);
		$this->setPrimaryPrice($object->GetID(), (double)$listing_type->GetDBField('Price'), $additiona_fields);
	}

	/**
	 * [HOOK] Deletes virtual product when listing type is deleted
	 *
	 * @param kEvent $event
	 */
	function OnDeleteListingType($event)
	{
		/** @var kDBItem $listing_type */
		$listing_type = $event->MasterEvent->getObject();

		$product_id = $listing_type->GetDBField('VirtualProductId');

		if ( $product_id ) {
			$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler');
			$temp_handler->DeleteItems($event->Prefix, $event->Special, Array ($product_id));
		}
	}

	/**
	 * Extends user membership in group when his order is approved
	 *
	 * @param kEvent $event
	 */
	function OnSubscriptionApprove($event)
	{
		$field_values = $event->getEventParam('field_values');
		$item_data = unserialize($field_values['ItemData']);

		if ( !getArrayValue($item_data,'PortalGroupId') ) {
			// is subscription product, but no group defined in it's properties
			trigger_error('Invalid product <b>'.$field_values['ProductName'].'</b> (id: '.$field_values['ProductId'].')', E_USER_WARNING);
			return false;
		}

		$sql = 'SELECT PortalUserId
				FROM ' . $this->Application->getUnitOption('ord', 'TableName') . '
				WHERE ' . $this->Application->getUnitOption('ord', 'IDField') . ' = ' . $field_values['OrderId'];
		$user_id = $this->Conn->GetOne($sql);

		$group_id = $item_data['PortalGroupId'];
		$duration = $item_data['Duration'];

		$sql = 'SELECT *
				FROM ' . TABLE_PREFIX . 'UserGroupRelations
				WHERE PortalUserId = ' . $user_id;
		$user_groups = $this->Conn->Query($sql, 'GroupId');

		if ( !isset($user_groups[$group_id]) ) {
			$expire = adodb_mktime() + $duration;
		}
		else {
			$expire = $user_groups[$group_id]['MembershipExpires'];
			$expire = $expire < adodb_mktime() ? adodb_mktime() + $duration : $expire + $duration;
		}

		/*// Customization healtheconomics.org
		if ($item_data['DurationType'] == 2) {
			$expire = $item_data['AccessExpiration'];
		}
		// Customization healtheconomics.org --*/

		$fields_hash = Array (
			'PortalUserId' => $user_id,
			'GroupId' => $group_id,
			'MembershipExpires' => $expire,
		);

		$this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'UserGroupRelations', 'REPLACE');

		$sub_order = $this->Application->recallObject('ord.-sub'.$event->getEventParam('next_sub_number'), 'ord');
		$sub_order->SetDBField('IsRecurringBilling', getArrayValue($item_data, 'IsRecurringBilling') ? 1 : 0);
		$sub_order->SetDBField('GroupId', $group_id);
		$sub_order->SetDBField('NextCharge_date', $expire);
		$sub_order->SetDBField('NextCharge_time', $expire);
	}

	function OnDownloadableApprove($event)
	{
		$field_values = $event->getEventParam('field_values');
		$product_id = $field_values['ProductId'];
		$sql = 'SELECT PortalUserId FROM '.$this->Application->getUnitOption('ord', 'TableName').'
				WHERE OrderId = '.$field_values['OrderId'];
		$user_id = $this->Conn->GetOne($sql);
		$sql = 'INSERT INTO '.TABLE_PREFIX.'UserFileAccess VALUES("", '.$product_id.', '.$user_id.')';
		$this->Conn->Query($sql);
	}

	protected function OnPackageApprove(kEvent $event)
	{
		$field_values = $event->getEventParam('field_values');
		$item_data = unserialize($field_values['ItemData']);
		$package_content_ids = $item_data['PackageContent'];

		/** @var ProductsItem $object_item */
		$object_item = $this->Application->recallObject('p.packageitem', null, array ('skip_autoload' => true));

		foreach ($package_content_ids as $package_item_id) {
			$object_field_values = array ();

			// query processing data from product and run approve event
			$sql = 'SELECT ProcessingData
					FROM ' . TABLE_PREFIX . 'Products
					WHERE ProductId = ' . $package_item_id;
			$processing_data = $this->Conn->GetOne($sql);

			if ( $processing_data ) {
				$processing_data = unserialize($processing_data);
				$approve_event = new kEvent($processing_data['ApproveEvent']);

				//$order_item_fields = $this->Conn->GetRow('SELECT * FROM '.TABLE_PREFIX.'OrderItems WHERE OrderItemId = '.$grouping_data[1]);
				$object_item->Load($package_item_id);

				$object_field_values['OrderId'] = $field_values['OrderId'];
				$object_field_values['ProductId'] = $package_item_id;

				$object_field_values['ItemData'] = serialize($item_data['PackageItemsItemData'][$package_item_id]);

				$approve_event->setEventParam('field_values', $object_field_values);
				$this->Application->HandleEvent($approve_event);
			}
		}
	}

	/**
	 * Saves edited item into temp table
	 * If there is no id, new item is created in temp table
	 *
	 * @param kEvent $event
	 * @return void
	 * @access protected
	 */
	protected function OnPreSave(kEvent $event)
	{
		$this->CheckRequiredOptions($event);

		parent::OnPreSave($event);
	}

	/**
	 * Set new price to ProductsPricing
	 *
	 * @param kEvent $event
	 * @return void
	 * @access protected
	 */
	protected function OnAfterItemCreate(kEvent $event)
	{
		parent::OnAfterItemCreate($event);

		$this->_updateProductPrice($event);
	}

	/**
	 * Set new price to ProductsPricing
	 *
	 * @param kEvent $event
	 * @return void
	 * @access protected
	 */
	protected function OnAfterItemUpdate(kEvent $event)
	{
		parent::OnAfterItemUpdate($event);

		$this->_updateProductPrice($event);
	}

	/**
	 * Updates product's primary price based on Price virtual field value
	 *
	 * @param kEvent $event
	 */
	function _updateProductPrice($event)
	{
		/** @var kDBItem $object */
		$object = $event->getObject();

		$price = $object->GetDBField('Price');

		// always create primary pricing, to show on Pricing tab (in admin) for tangible products
		$force_create = ($object->GetDBField('Type') == PRODUCT_TYPE_TANGIBLE) && is_null($price);

		if ($force_create || ($price != $object->GetOriginalField('Price'))) {
			// new product OR price was changed in virtual field
			$this->setPrimaryPrice($object->GetID(), (float)$price);
		}
	}

	function CheckRequiredOptions($event)
	{
		$object = $event->getObject();
		if ($object->GetDBField('ProductId') == '') return ; // if product does not have ID - it's not yet created
		$opt_object = $this->Application->recallObject('po', null, Array('skip_autoload' => true) );
		$has_required = $this->Conn->GetOne('SELECT COUNT(*) FROM '.$opt_object->TableName.' WHERE Required = 1 AND ProductId = '.$object->GetDBField('ProductId'));
		//we need to imitate data sumbit, as parent' PreSave sets object values from $items_info
		$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
		$items_info[$object->GetDBField('ProductId')]['HasRequiredOptions'] = $has_required ? '1' : '0';
		$this->Application->SetVar($event->getPrefixSpecial(true), $items_info);
		$object->SetDBField('HasRequiredOptions', $has_required ? 1 : 0);
	}

	/**
	 * Sets required price in primary price backed, if it's missing, then create it
	 *
	 * @param int $product_id
	 * @param double $price
	 * @param Array $additional_fields
	 * @return bool
	 */
	function setPrimaryPrice($product_id, $price, $additional_fields = Array())
	{
		/** @var kDBItem $pr_object */
		$pr_object = $this->Application->recallObject('pr.-item', null, Array('skip_autoload' => true) );

		$pr_object->Load( Array('ProductId' => $product_id, 'IsPrimary' => 1) );

		$sql = 'SELECT COUNT(*) FROM '.$pr_object->TableName.' WHERE ProductId = '.$product_id;
		$has_pricings = $this->Conn->GetOne($sql);

		if ($additional_fields) {
			$pr_object->SetDBFieldsFromHash($additional_fields);
		}

		if( ($price === false) && $has_pricings ) return false;

		if( $pr_object->isLoaded() )
		{
			$pr_object->SetField('Price', $price);
			return $pr_object->Update();
		}
		else
		{
			$group_id = $this->Application->ConfigValue('User_LoggedInGroup');
			$field_values = Array('ProductId' => $product_id, 'IsPrimary' => 1, 'MinQty' => 1, 'MaxQty' => -1, 'GroupId'=>$group_id);
			$pr_object->SetDBFieldsFromHash($field_values);
			$pr_object->SetField('Price', $price);

			return $pr_object->Create();
		}
	}

	/**
	 * Occurs after deleting item, id of deleted item
	 * is stored as 'id' param of event
	 *
	 * @param kEvent $event
	 * @return void
	 * @access protected
	 */
	protected function OnAfterItemDelete(kEvent $event)
	{
		parent::OnAfterItemDelete($event);

		$product_id = $event->getEventParam('id');
		if ( !$product_id ) {
			return;
		}

		$sql = 'DELETE FROM ' . TABLE_PREFIX . 'UserFileAccess
				WHERE ProductId = ' . $product_id;
		$this->Conn->Query($sql);
	}

	/**
	 * Load price from temp table if product mode is temp table
	 *
	 * @param kEvent $event
	 */

	/**
	 * Load price from temp table if product mode is temp table
	 *
	 * @param kEvent $event
	 * @return void
	 * @access protected
	 */
	protected function OnAfterItemLoad(kEvent $event)
	{
		parent::OnAfterItemLoad($event);

		/** @var ProductsItem $object */
		$object = $event->getObject();

		$a_pricing = $object->getPrimaryPricing();
		if ( !$a_pricing ) {
			// pricing doesn't exist for new products
			$price = $cost = null;
		}
		else {
			$price = (float)$a_pricing['Price'];
			$cost = (float)$a_pricing['Cost'];
		}

		// set original fields to use them in OnAfterItemCreate/OnAfterItemUpdate later
		$object->SetDBField('Price', $price);
		$object->SetOriginalField('Price', $price);

		$object->SetDBField('Cost', $cost);
		$object->SetOriginalField('Cost', $cost);
	}

	/**
	 * Allows to add products to package besides all that parent method does
	 *
	 * @param kEvent $event
	 */
	function OnProcessSelected($event)
	{
		$dst_field = $this->Application->RecallVar('dst_field');

		if ($dst_field == 'PackageContent') {
			$this->OnAddToPackage($event);
		}
		elseif ($dst_field == 'AssignedCoupon') {
			$coupon_id = $this->Application->GetVar('selected_ids');
			$object = $event->getObject();
			$object->SetDBField('AssignedCoupon', $coupon_id);
			$this->RemoveRequiredFields($object);
			$object->Update();
		}
		else {
			parent::OnProcessSelected($event);
		}
		$this->finalizePopup($event);
	}

	/**
	 * Called when some products are selected in products selector for this prefix
	 *
	 * @param kEvent $event
	 */
	function OnAddToPackage($event)
	{
		$selected_ids = $this->Application->GetVar('selected_ids');

		// update current package content with selected products

		/** @var ProductsItem $object */
		$object = $event->getObject();

		$product_ids = $selected_ids['p'] ? explode(',', $selected_ids['p']) : Array();

		if ($product_ids) {
			$current_ids = $object->GetPackageContentIds();
			$current_ids = array_unique(array_merge($current_ids, $product_ids));

			// remove package product from selected list
			$this_product = array_search($object->GetID(), $current_ids);
			if ($this_product !== false) {
				unset($current_ids[$this_product]);
			}

			$dst_field = $this->Application->RecallVar('dst_field');
			$object->SetDBField($dst_field, '|'.implode('|', $current_ids).'|');

			$object->Update();
			$this->ProcessPackageItems($event);
		}

		$this->finalizePopup($event);
	}


	function ProcessPackageItems(kEvent $event)
	{
		//$this->Application->SetVar('p_mode', 't');

		/** @var ProductsItem $object */
		$object = $event->getObject();

		$content_ids = $object->GetPackageContentIds();

		if (sizeof($content_ids) > 0) {
			$total_weight = $this->Conn->GetOne('SELECT SUM(Weight) FROM '.TABLE_PREFIX.'Products WHERE ProductId IN ('.implode(', ', $content_ids).') AND Type=1');

			if (!$total_weight) $total_weight = 0;

			$this->Conn->Query('UPDATE '.$object->TableName.' SET Weight='.$total_weight.' WHERE ProductId='.$object->GetID());
		}

		/*
		$this->Application->SetVar('p_mode', false);

		$list = $this->Application->recallObject('p.content', 'p_List', array('types'=>'content'));

		$this->Application->SetVar('p_mode', 't');

		$list->Query();

		$total_weight_a = 0;
		$total_weight_b = 0;

		$list->GoFirst();

		while (!$list->EOL())
		{
			if ($list->GetDBField('Type')==1){
				$total_weight_a += $list->GetField('Weight_a');
				$total_weight_b += $list->GetField('Weight_b');
			}
			$list->GoNext();
		}

		$object->SetField('Weight_a', $total_weight_a);
		$object->SetField('Weight_b', $total_weight_b);
		*/
		//$object->Update();


	}

	/**
	 * Enter description here...
	 *
	 * @param kEvent $event
	 */

	function OnSaveItems($event)
	{
		//$event->CallSubEvent('OnUpdate');
		$event->redirect = false;
		//$event->setRedirectParams(Array ('opener' => 's', 'pass' => 'all,p'));
	}

	/**
	 * Removes product from package
	 *
	 * @param kEvent $event
	 */
	function OnRemovePackageItem($event) {

		$this->Application->SetVar('p_mode', 't');

		$object = $event->getObject();

		$items_info = $this->Application->GetVar('p_content');

		if($items_info)
		{
			$product_ids = array_keys($items_info);

			$current_ids = $object->GetPackageContentIds();

			$current_ids_flip = array_flip($current_ids);
			foreach($product_ids as $key=>$val){
				unset($current_ids_flip[$val]);
			}
			$current_ids = array_keys($current_ids_flip);
			$current_ids_str = '|'.implode('|', array_unique($current_ids)).'|';
			$object->SetDBField('PackageContent', $current_ids_str);
		}

		$object->Update();
		$this->ProcessPackageItems($event);
	}

	/**
	 * Occurs before deleting item, id of item being
	 * deleted is stored as 'id' event param
	 *
	 * @param kEvent $event
	 * @return void
	 * @access protected
	 */
	protected function OnBeforeItemDelete(kEvent $event)
	{
		parent::OnBeforeItemDelete($event);

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

		$sql = 'SELECT COUNT(*)
				FROM ' . TABLE_PREFIX . 'Products
				WHERE PackageContent LIKE "%|' . $object->GetID() . '%"';
		$product_includes_in = $this->Conn->GetOne($sql);

		if ( $product_includes_in > 0 ) {
			$event->status = kEvent::erFAIL;
		}
	}

	/**
	 * Returns specific to each item type columns only
	 *
	 * @param kEvent $event
	 * @return Array
	 * @access protected
	 */
	public function getCustomExportColumns(kEvent $event)
	{
		$columns = parent::getCustomExportColumns($event);

		$new_columns = Array (
			'__VIRTUAL__Price' => 'Price',
			'__VIRTUAL__Cost' => 'Cost',
		);

		return array_merge($columns, $new_columns);
	}

/**
	 * Sets non standart virtual fields (e.g. to other tables)
	 *
	 * @param kEvent $event
	 */
	function setCustomExportColumns($event)
	{
		parent::setCustomExportColumns($event);

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

		$this->setPrimaryPrice($object->GetID(), (double)$object->GetDBField('Price'), Array ('Cost' => (double)$object->GetDBField('Cost')));
	}

	function OnPreSaveAndOpenPopup($event)
	{
		/** @var kDBItem $object */
		$object = $event->getObject();

		$this->RemoveRequiredFields($object);
		$event->CallSubEvent('OnPreSave');

		$event->redirect = $this->Application->GetVar('t');
		// pass ID too, in case if product is created by OnPreSave call to ensure proper editing
		$event->SetRedirectParam('pass', 'all');
		$event->SetRedirectParam($event->getPrefixSpecial(true) . '_id', $object->GetID());
	}


	/**
	 * Returns ID of current item to be edited
	 * by checking ID passed in get/post as prefix_id
	 * or by looking at first from selected ids, stored.
	 * Returned id is also stored in Session in case
	 * it was explicitly passed as get/post
	 *
	 * @param kEvent $event
	 * @return int
	 * @access public
	 */
	public function getPassedID(kEvent $event)
	{
		if ( $this->Application->isAdminUser ) {
			$event->setEventParam('raise_warnings', 0);
		}

		$passed = parent::getPassedID($event);

		if ( $passed ) {
			return $passed;
		}

		if ( $this->Application->isAdminUser ) {
			// we may get product id out of OrderItem, if it exists
			/** @var OrdersItem $ord_item */
			$ord_item = $this->Application->recallObject('orditems', null, Array ('raise_warnings' => 0));

			if ( $ord_item->GetDBField('ProductId') ) {
				$passed = $ord_item->GetDBField('ProductId');
			}
		}

		return $passed;
	}

	/**
	 * Occurs, when config was parsed, allows to change config data dynamically
	 *
	 * @param kEvent $event
	 * @return void
	 * @access protected
	 */
	protected function OnAfterConfigRead(kEvent $event)
	{
		parent::OnAfterConfigRead($event);

		if (!$this->Application->LoggedIn()) {
			return ;
		}

		$user_id = $this->Application->RecallVar('user_id');

		$sql = 'SELECT PrimaryGroupId
				FROM ' . TABLE_PREFIX . 'Users
				WHERE PortalUserId = ' . $user_id;
		$primary_group_id = $this->Conn->GetOne($sql);

		if (!$primary_group_id) {
			return;
		}

		$sub_select = '	SELECT pp.Price
						FROM ' . TABLE_PREFIX . 'ProductsPricing AS pp
			 			WHERE pp.ProductId = %1$s.ProductId AND GroupId = ' . $primary_group_id . '
			 			ORDER BY MinQty
			 			LIMIT 0,1';

		$calculated_fields = $this->Application->getUnitOption($event->Prefix, 'CalculatedFields');
		$calculated_fields['']['Price'] = 'IFNULL((' . $sub_select . '), ' . $calculated_fields['']['Price'] . ')';
		$this->Application->setUnitOption($event->Prefix, 'CalculatedFields', $calculated_fields);
	}

	/**
	 * Starts product editing, remove any pending inventory actions
	 *
	 * @param kEvent $event
	 * @return void
	 * @access protected
	 */
	protected function OnEdit(kEvent $event)
	{
		$this->Application->RemoveVar('inventory_actions');

		parent::OnEdit($event);
	}

	/**
	 * Adds "Shop Cart" tab on paid listing type editing tab
	 *
	 * @param kEvent $event
	 */
	function OnModifyPaidListingConfig($event)
	{
		$edit_tab_presets = $this->Application->getUnitOption($event->MasterEvent->Prefix, 'EditTabPresets');
		$edit_tab_presets['Default']['shopping_cart'] = Array ('title' => 'la_tab_ShopCartEntry', 't' => 'in-commerce/paid_listings/paid_listing_type_shopcart', 'priority' => 2);
		$this->Application->setUnitOption($event->MasterEvent->Prefix, 'EditTabPresets', $edit_tab_presets);
	}

	/**
	 * [HOOK] Allows to add cloned subitem to given prefix
	 *
	 * @param kEvent $event
	 * @return void
	 * @access protected
	 */
	protected function OnCloneSubItem(kEvent $event)
	{
		parent::OnCloneSubItem($event);

		if ( $event->MasterEvent->Prefix == 'rev' ) {
			$clones = $this->Application->getUnitOption($event->MasterEvent->Prefix, 'Clones');
			$subitem_prefix = $event->Prefix . '-' . $event->MasterEvent->Prefix;

			$clones[$subitem_prefix]['ConfigMapping'] = Array (
				'PerPage'				=>	'Comm_Perpage_Reviews',

				'ReviewDelayInterval'	=>	'product_ReviewDelay_Value',
				'ReviewDelayValue'		=>	'product_ReviewDelay_Interval',
			);

			$this->Application->setUnitOption($event->MasterEvent->Prefix, 'Clones', $clones);
		}
	}

	/**
	 * Adds product to comparison list
	 *
	 * @param kEvent $event
	 * @return void
	 * @access protected
	 */
	protected function OnAddToCompare(kEvent $event)
	{
		$products = $this->getCompareProducts();
		$product_id = (int)$this->Application->GetVar($event->Prefix . '_id');

		if ( $product_id ) {
			$max_products = $this->Application->ConfigValue('MaxCompareProducts');

			if ( count($products) < $max_products ) {
				$products[] = $product_id;
				$this->Application->Session->SetCookie('compare_products', implode('|', array_unique($products)));

				$event->SetRedirectParam('result', 'added');
			}
			else {
				$event->SetRedirectParam('result', 'error');
			}
		}

		$event->SetRedirectParam('pass', 'm,p');
	}

	/**
	 * Adds product to comparison list
	 *
	 * @param kEvent $event
	 * @return void
	 * @access protected
	 */
	protected function OnRemoveFromCompare(kEvent $event)
	{
		$products = $this->getCompareProducts();

		$product_id = (int)$this->Application->GetVar($event->Prefix . '_id');

		if ( $product_id && in_array($product_id, $products) ) {
			$products = array_diff($products, Array ($product_id));
			$this->Application->Session->SetCookie('compare_products', implode('|', array_unique($products)));

			$event->SetRedirectParam('result', 'removed');
		}

		$event->SetRedirectParam('pass', 'm,p');
	}

	/**
	 * Cancels product compare
	 *
	 * @param kEvent $event
	 * @return void
	 * @access protected
	 */
	protected function OnCancelCompare(kEvent $event)
	{
		$this->Application->Session->SetCookie('compare_products', '', -1);

		$event->SetRedirectParam('result', 'all_removed');
	}

	/**
	 * Returns products, that needs to be compared with each other
	 *
	 * @return Array
	 * @access protected
	 */
	protected function getCompareProducts()
	{
		$products = $this->Application->GetVarDirect('compare_products', 'Cookie');
		$products = $products ? explode('|', $products) : Array ();

		return $products;
	}
}
