<?php
/**
* @version	$Id: reviews_event_handler.php 15065 2012-01-17 15:11:29Z alex $
* @package	In-Portal
* @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license      GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/

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

	class ReviewsEventHandler extends kDBEventHandler
	{
		/**
		 * Returns special of main item for linking with sub-item
		 *
		 * @param kEvent $event
		 * @return string
		 * @access protected
		 */
		protected function getMainSpecial(kEvent &$event)
		{
			if ( $event->Special == 'product' && !$this->Application->isAdmin ) {
				// rev.product should auto-link
				return '';
			}

			return parent::getMainSpecial($event);
		}

		/**
		 * Checks REVIEW/REVIEW.PENDING permission by main object primary category (not current category)
		 *
		 * @param kEvent $event
		 * @return bool
		 * @access public
		 */
		public function CheckPermission(kEvent &$event)
		{
			if ( $event->Name == 'OnAddReview' || $event->Name == 'OnCreate' ) {
				$perm_helper =& $this->Application->recallObject('PermissionsHelper');
				/* @var $perm_helper kPermissionsHelper */

				$parent_prefix = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix');
				$main_object =& $this->Application->recallObject($parent_prefix);
				/* @var $main_object kCatDBItem */

				$perm_name = $this->getPermPrefix($event).'.REVIEW';
				$res = 	$this->Application->CheckPermission($perm_name, 0, $main_object->GetDBField('CategoryId')) ||
						$this->Application->CheckPermission($perm_name.'.PENDING', 0, $main_object->GetDBField('CategoryId'));

				if ( !$res ) {
					$event->status = kEvent::erPERM_FAIL;
				}

				return $res;
			}

			$check_events = Array (
				'OnItemBuild', 'OnUpdate', /*'OnMassApprove', 'OnMassDecline'*/
			);

			$perm_category = $this->_getReviewCategory($event);

			if ( in_array($event->Name, $check_events) ) {
				// check for PRODUCT.VIEW permission

				$perm_helper =& $this->Application->recallObject('PermissionsHelper');
				/* @var $perm_helper kPermissionsHelper */

				$perm_prefix = $this->getPermPrefix($event);

				if ( $perm_category === false ) {
					// no item id present -> allow
					return true;
				}

				switch ($event->Name) {
					case 'OnItemBuild':
						$res = $this->Application->CheckPermission($perm_prefix . '.VIEW', 0, $perm_category);
						break;

					case 'OnUpdate':
					case 'OnMassApprove':
					case 'OnMassDecline':
						$res = 	$this->Application->CheckPermission($perm_prefix . '.ADD', 0, $perm_category) ||
								$this->Application->CheckPermission($perm_prefix . '.MODIFY', 0, $perm_category);
						break;

					default:
						$res = false;
						break;
				}

				if ( !$res ) {
					$event->status = kEvent::erPERM_FAIL;
				}

				return $res;

			}

			return parent::CheckPermission($event);
		}

		/**
		 * Returns primary category of review's main item
		 *
		 * @param kEvent $event
		 * @return int
		 */
		function _getReviewCategory(&$event)
		{
			$items_info = $this->Application->GetVar($event->getPrefixSpecial());

			if ($items_info) {
				// rev:PresetFormFields is used to initialize new review creation
				list ($review_id, ) = each($items_info);
			}
			else {
				// when adding new review in admin
				$review_id = false;

			}

			if (!$review_id) {
				return false;
			}

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

			// 1. get main item resource id (use object, because of temp tables in admin)
			$sql = 'SELECT ItemId
					FROM ' . $object->TableName . '
					WHERE ' . $object->IDField . ' = ' . $review_id;
			$resource_id = $this->Conn->GetOne($sql);

			// 2. set main item id (for permission checks)
			$parent_prefix = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix');
			$sql = 'SELECT ' . $this->Application->getUnitOption($parent_prefix, 'IDField') .'
					FROM ' . $this->Application->getUnitOption($parent_prefix, 'TableName') .'
					WHERE ResourceId = ' . $resource_id;
			$this->Application->SetVar($parent_prefix . '_id', $this->Conn->GetOne($sql));

			// 3. get main item category
			$sql = 'SELECT CategoryId
					FROM ' . $this->Application->getUnitOption('ci', 'TableName') .'
					WHERE ItemResourceId = ' . $resource_id .' AND PrimaryCat = 1';
			return $this->Conn->GetOne($sql);
		}

		/**
		 * Returns prefix for permissions
		 *
		 * @param kEvent $event
		 */
		function getPermPrefix(&$event)
		{
			$main_prefix = $this->Application->GetTopmostPrefix($event->Prefix, true);
			// this will return LINK for l, ARTICLE for n, TOPIC for bb, PRODUCT for p

			return $this->Application->getUnitOption($main_prefix, 'PermItemPrefix');
		}

		/**
		 * Apply any custom changes to list's sql query
		 *
		 * @param kEvent $event
		 * @access protected
		 * @see OnListBuild
		 */
		protected function SetCustomQuery(kEvent &$event)
		{
			parent::SetCustomQuery($event);

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

			if ( !$this->Application->isAdminUser ) {
				$object->addFilter('active', '%1$s.Status = ' . STATUS_ACTIVE);
			}

			switch ($event->Special) {
				case 'showall':
					$object->clearFilters();
					break;

				case 'item': // used ?
					$object->clearFilters();
					$parent_info = $object->getLinkedInfo();

					$parent =& $this->Application->recallObject($parent_info['ParentPrefix']);
					/* @var $parent kDBItem */

					$object->addFilter('item_reviews', '%1$s.ItemId = ' . $parent->GetDBField('ResourceId'));
					break;

				case 'products': // used in In-Portal (Structure & Data -> Reviews section)
					$object->removeFilter('parent_filter'); // this is important
					$object->addFilter('product_reviews', 'pr.ResourceId IS NOT NULL');
					break;
			}

			if ( preg_match('/(.*)-rev/', $event->Prefix, $regs) ) {
				// "Structure & Data" -> "Reviews" (section in K4)
				$item_type = $this->Application->getUnitOption($regs[1], 'ItemType');
				$object->addFilter('itemtype_filter', '%1$s.ItemType = ' . $item_type);

				if ( $this->Application->isAdmin ) {
					// temporarily solution so we can see sub-items on separate grid in Admin
					$object->removeFilter('parent_filter');
				}
			}

			if ( $event->getEventParam('type') == 'current_user' ) {
				$object->addFilter('current_user', '%1$s.CreatedById = ' . $this->Application->RecallVar('user_id'));
				$object->addFilter('current_ip', '%1$s.IPAddress = "' . $_SERVER['REMOTE_ADDR'] . '"');
			}
		}

		/**
		 * Adds review from front in case if user is logged in
		 *
		 * @param kEvent $event
		 */
		function OnAddReview(&$event)
		{
			$event->CallSubEvent('OnCreate');
		}

		/**
		 * Get new review status on user review permission
		 *
		 * @param kEvent $event
		 * @return int
		 */
		function getReviewStatus(&$event)
		{
			$parent_prefix = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix');
			$main_object =& $this->Application->recallObject($parent_prefix);
			/* @var $main_object kCatDBItem */

			$ret = STATUS_DISABLED;
			$perm_name = $this->getPermPrefix($event).'.REVIEW';
			if ($this->Application->CheckPermission($perm_name, 0, $main_object->GetDBField('CategoryId'))) {
				$ret = STATUS_ACTIVE;
			}
			else if ($this->Application->CheckPermission($perm_name.'.PENDING', 0, $main_object->GetDBField('CategoryId'))) {
				$ret = STATUS_PENDING;
			}

			return $ret;
		}

		/**
		 * Prefills all fields on front-end
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnBeforeItemCreate(kEvent &$event)
		{
			parent::OnBeforeItemCreate($event);

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

			$parent_info = $object->getLinkedInfo();
			$item_type = $this->Application->getUnitOption($parent_info['ParentPrefix'], 'ItemType');

			$object->SetDBField('IPAddress', $_SERVER['REMOTE_ADDR']);
			$object->SetDBField('ItemType', $item_type);
			$object->SetDBField('Module', $this->Application->findModule('Var', $parent_info['ParentPrefix'], 'Name'));

			if ( $this->Application->isAdminUser ) {
				// don't perform spam control on admin
				return ;
			}

			$spam_helper =& $this->Application->recallObject('SpamHelper');
			/* @var $spam_helper SpamHelper */

			$spam_helper->InitHelper($parent_info['ParentId'], 'Review', 0);

			if ( $spam_helper->InSpamControl() ) {
				$event->status = kEvent::erFAIL;
				$object->SetError('ReviewText', 'too_frequent', 'lu_ferror_review_duplicate');
				return;
			}

			$rating = $object->GetDBField('Rating');
			if ( $rating < 1 || $rating > 5 ) {
				$object->SetDBField('Rating', null);
			}

			$object->SetDBField('ItemId', $parent_info['ParentId']); // ResourceId
			$object->SetDBField('CreatedById', $this->Application->RecallVar('user_id'));

			$object->SetDBField('Status', $this->getReviewStatus($event));
			$object->SetDBField('TextFormat', 0); // set plain text format directly
		}

		/**
		 * Sets correct rating value
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnBeforeItemUpdate(kEvent &$event)
		{
			parent::OnBeforeItemUpdate($event);

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

			$rating = $object->GetDBField('Rating');

			if ( !$rating ) {
				$object->SetDBField('Rating', null);
			}
		}

		/**
		 * Updates item review counter
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnAfterItemCreate(kEvent &$event)
		{
			parent::OnAfterItemCreate($event);

			$this->updateSubitemCounters($event);

			if ( !$this->Application->isAdminUser ) {
				$spam_helper =& $this->Application->recallObject('SpamHelper');
				/* @var $spam_helper SpamHelper */

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

				$parent_info = $object->getLinkedInfo();

				$config_mapping = $this->Application->getUnitOption($event->Prefix, 'ConfigMapping');
				$review_settings = $config_mapping['ReviewDelayValue'] . ':' . $config_mapping['ReviewDelayInterval'];
				$spam_helper->InitHelper($parent_info['ParentId'], 'Review', $review_settings);

				$spam_helper->AddToSpamControl();

				$review_status = $object->GetDBField('Status');

				if ( $review_status == STATUS_ACTIVE || $review_status == STATUS_PENDING ) {
					$email_event = $this->getPermPrefix($event) . '.REVIEW.' . ($review_status == STATUS_ACTIVE ? 'ADD' : 'ADD.PENDING');
					$this->Application->EmailEventUser($email_event, $object->GetDBField('CreatedById'));
					$this->Application->EmailEventAdmin($email_event);
				}
			}
		}

		/**
		 * Updates item review counter
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnAfterItemUpdate(kEvent &$event)
		{
			parent::OnAfterItemUpdate($event);

			$this->updateSubitemCounters($event);

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

			if ( $this->Application->isAdminUser && !$object->IsTempTable() ) {
				// send email on review status change from reviews grid in admin
				$review_status = $object->GetDBField('Status');
				$process_status = Array (STATUS_ACTIVE, STATUS_DISABLED);

				if ( ($review_status != $object->GetOriginalField('Status')) && in_array($review_status, $process_status) ) {
					$this->_loadMainObject($event);

					$email_event = $this->getPermPrefix($event) . '.REVIEW.' . ($review_status == STATUS_ACTIVE ? 'APPROVE' : 'DENY');
					$this->Application->EmailEventUser($email_event, $object->GetDBField('CreatedById'));
				}
			}
		}

		/**
		 * Loads main object of review (link, article, etc.)
		 *
		 * @param kEvent $event
		 * @return kCatDBItem
		 */
		function _loadMainObject(&$event)
		{
			$object =& $event->getObject();
			/* @var $object kDBItem */

			$parent_prefix = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix');
			$parent_table_key = $this->Application->getUnitOption($event->Prefix, 'ParentTableKey');
			$foreign_key = $this->Application->getUnitOption($event->Prefix, 'ForeignKey');

			$main_object =& $this->Application->recallObject($parent_prefix, null, Array ('skip_autoload' => true));
			/* @var $main_object kDBItem */

			$main_object->Load($object->GetDBField($foreign_key), $parent_table_key);
		}

		/**
		 * Updates total review counter, cached rating, votes count
		 *
		 * @param kEvent $event
		 */
		function updateSubitemCounters(&$event)
		{
			$parent_prefix = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix');
			$main_object =& $this->Application->recallObject($parent_prefix, null, Array ('raise_warnings' => 0));
			/* @var $main_object kCatDBItem */

			if (!$main_object->isLoaded()) {
				// deleting main item / cloning main item
				return ;
			}

			$object =& $event->getObject(); // for temp tables
			/* @var $object kDBItem */

			// 1. update review counter
			$sql = 'SELECT COUNT(ReviewId)
					FROM '.$object->TableName.'
					WHERE ItemId = '.$main_object->GetDBField('ResourceId');
			$review_count = $this->Conn->GetOne($sql);
			$main_object->SetDBField('CachedReviewsQty', $review_count);

			// 2. update votes counter + rating
			$rating = $object->GetDBField('Rating');
			$avg_rating = $main_object->GetDBField('CachedRating');
			$votes_count = $main_object->GetDBField('CachedVotesQty');

			switch ($event->Name) {
				case 'OnAfterItemCreate': // adding new review with rating
					$this->changeRating($avg_rating, $votes_count, $rating, '+');
					break;

				case 'OnAfterItemDelete':
					$this->changeRating($avg_rating, $votes_count, $rating, '-');
					break;

				case 'OnAfterItemUpdate':
					$this->changeRating($avg_rating, $votes_count, $object->GetOriginalField('Rating'), '-');
					$this->changeRating($avg_rating, $votes_count, $rating, '+');
					break;
			}
			$main_object->SetDBField('CachedRating', "$avg_rating"); // covert $avg_rating value to string
			$main_object->SetDBField('CachedVotesQty', $votes_count);
			$main_object->Update();
		}

		/**
		 * Changes average rating and votes count based on requested operation
		 *
		 * @param float $avg_rating average rating before new vote
		 * @param int $votes_count votes count before new vote
		 * @param int $rating new vote (from 1 to 5)
		 * @param string $operation requested operation (+ / -)
		 */
		function changeRating(&$avg_rating, &$votes_count, $rating, $operation)
		{
			if ($rating < 1 || $rating > 5) {
				return ;
			}

			if ($operation == '+') {
				$avg_rating = (($avg_rating * $votes_count) + $rating) / ($votes_count + 1);
				++$votes_count;
			}
			else {
				$avg_rating = (($avg_rating * $votes_count) - $rating) / ($votes_count - 1);
				--$votes_count;
			}
		}

		/**
		 * Updates main item cached review counter
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnAfterItemDelete(kEvent &$event)
		{
			parent::OnAfterItemDelete($event);

			$this->updateSubitemCounters($event);
		}

		/**
		 * Creates review & redirect to confirmation template
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnCreate(kEvent &$event)
		{
			parent::OnCreate($event);

			if ( $event->status != kEvent::erSUCCESS || $this->Application->isAdmin ) {
				return;
			}

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

			if ( $this->Application->GetVar('ajax') == 'yes' ) {
				$ajax_form_helper =& $this->Application->recallObject('AjaxFormHelper');
				/* @var $ajax_form_helper AjaxFormHelper */

				$params = Array ('status' => 'OK');

				if ( $event->status != kEvent::erSUCCESS ) {
					$ajax_form_helper->prepareJSONErrors($event, $params);
				}

				// let FormManager decide what template to show
				$params['review_status'] = $object->GetDBField('Status');

				$ajax_form_helper->sendResponse($event, $params);
			}
			else {
				$event->SetRedirectParam('opener', 's');
				$next_template = $object->GetDBField('Status') == STATUS_ACTIVE ? 'success_template' : 'success_pending_template';
				$event->redirect = $this->Application->GetVar($next_template);

				$parent_prefix = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix');
				$event->SetRedirectParam('pass', 'm,'.$parent_prefix);
			}
		}

		/**
		 * Makes left join to item's table, when in separate grid
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnAfterConfigRead(kEvent &$event)
		{
			parent::OnAfterConfigRead($event);

			if (preg_match('/(.*)-rev/', $event->Prefix, $regs) && $this->Application->prefixRegistred($regs[1])) {
				// "Structure & Data" -> "Reviews" (section in K4)

				// 1. add join to items table (for "Structure & Data" -> "Reviews" section)
				$item_table = $this->Application->getUnitOption($regs[1], 'TableName');
				$ci_table = $this->Application->getUnitOption('ci', 'TableName');

				$list_sqls = $this->Application->getUnitOption($event->Prefix, 'ListSQLs');
				$list_sqls[''] .= ' LEFT JOIN '.$item_table.' item_table ON item_table.ResourceId = %1$s.ItemId';
				$list_sqls[''] .= ' LEFT JOIN '.$ci_table.' ci ON item_table.ResourceId = ci.ItemResourceId AND ci.PrimaryCat = 1';
				$this->Application->setUnitOption($event->Prefix, 'ListSQLs', $list_sqls);

				// 2. add calculated field
				$calculated_fields = $this->Application->getUnitOption($event->Prefix, 'CalculatedFields');
				$calculated_fields['']['CatalogItemName'] = 'item_table.' . $this->getTitleField($regs[1]);
				$calculated_fields['']['CatalogItemId'] = 'item_table.' . $this->Application->getUnitOption($regs[1], 'IDField');
				$calculated_fields['']['CatalogItemCategory'] = 'ci.CategoryId';
				$this->Application->setUnitOption($event->Prefix, 'CalculatedFields', $calculated_fields);
			}
		}

		/**
		 * Convert TitleField field of kMultiLanguage formatter used for it
		 *
		 * @param string $prefix
		 * @return string
		 */
		function getTitleField($prefix)
		{
			$lang_prefix = 'l'.$this->Application->GetVar('m_lang').'_';

			$title_field = $this->Application->getUnitOption($prefix, 'TitleField');
			$field_options = $this->Application->getUnitOption($prefix.'.'.$title_field, 'Fields');

			$formatter_class = isset($field_options['formatter']) ? $field_options['formatter'] : '';
			if ($formatter_class == 'kMultiLanguage' && !isset($field_options['master_field'])) {
				$title_field = $lang_prefix.$title_field;
			}
			return $title_field;
		}

		/**
		 * Set's new perpage for Category Item Reviews (used on Front-end)
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnSetPerPage(kEvent &$event)
		{
			parent::OnSetPerPage($event);

			$parent_prefix = $event->Application->getUnitOption($event->Prefix, 'ParentPrefix');
			$event->SetRedirectParam('pass', 'm,' . $event->getPrefixSpecial() . ',' . $parent_prefix);
		}
	}